From 3f8d35d7271664681fb295767ee8d3c75e3373cd Mon Sep 17 00:00:00 2001 From: sampath1117 Date: Thu, 5 Sep 2024 14:54:33 +0000 Subject: [PATCH 1/3] add support for dilate in HOST backend --- CHANGELOG.md | 8 +- CMakeLists.txt | 2 +- include/rpp_version.h | 4 +- include/rppt_tensor_effects_augmentations.h | 4 + .../rppt_tensor_morphological_operations.h | 23 + src/include/cpu/rpp_cpu_common.hpp | 16 +- src/include/cpu/rpp_cpu_filter.hpp | 467 +++ src/include/cpu/rpp_cpu_simd.hpp | 168 +- .../host_tensor_morphological_operations.hpp | 30 + src/modules/cpu/kernel/dilate.hpp | 2775 +++++++++++++++++ .../rppt_tensor_morphological_operations.cpp | 66 + utilities/test_suite/HIP/Tensor_hip.cpp | 25 +- utilities/test_suite/HIP/runTests.py | 20 +- utilities/test_suite/HOST/Tensor_host.cpp | 26 +- utilities/test_suite/HOST/runTests.py | 22 +- utilities/test_suite/common.py | 4 +- utilities/test_suite/rpp_test_suite_common.h | 14 +- 17 files changed, 3628 insertions(+), 46 deletions(-) create mode 100644 src/include/cpu/rpp_cpu_filter.hpp create mode 100644 src/modules/cpu/host_tensor_morphological_operations.hpp create mode 100644 src/modules/cpu/kernel/dilate.hpp diff --git a/CHANGELOG.md b/CHANGELOG.md index b8fadf597..4464281a8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,13 @@ # Changelog for RPP Full documentation for RPP is available at [https://rocm.docs.amd.com/projects/rpp/en/latest](https://rocm.docs.amd.com/projects/rpp/en/latest) - + +## RPP 1.15.3 (unreleased) + +### Changes + +* RPP Tensor Erode support on HOST + ## RPP 1.9.1 for ROCm 6.3.0 ### Changes diff --git a/CMakeLists.txt b/CMakeLists.txt index 7ef88e169..1e32dc323 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -29,7 +29,7 @@ endif() set(CMAKE_CXX_STANDARD 17) # RPP Version -set(VERSION "1.9.1") +set(VERSION "1.15.3") # Set Project Version and Language project(rpp VERSION ${VERSION} LANGUAGES CXX) diff --git a/include/rpp_version.h b/include/rpp_version.h index 79e0b248d..71a963071 100644 --- a/include/rpp_version.h +++ b/include/rpp_version.h @@ -39,8 +39,8 @@ extern "C" { #endif // NOTE: IMPORTANT: Match the version with CMakelists.txt version #define RPP_VERSION_MAJOR 1 -#define RPP_VERSION_MINOR 9 -#define RPP_VERSION_PATCH 1 +#define RPP_VERSION_MINOR 15 +#define RPP_VERSION_PATCH 3 #ifdef __cplusplus } #endif diff --git a/include/rppt_tensor_effects_augmentations.h b/include/rppt_tensor_effects_augmentations.h index f6f0379fb..8f1f2f696 100644 --- a/include/rppt_tensor_effects_augmentations.h +++ b/include/rppt_tensor_effects_augmentations.h @@ -518,7 +518,11 @@ RppStatus rppt_jitter_gpu(RppPtr_t srcPtr, RpptDescPtr srcDescPtr, RppPtr_t dstP * \param [in] dstGenericDescPtr destination tensor descriptor * \param [in] meanTensor mean values for each input, which are used to compute the generalized Box-Mueller transforms in a gaussian distribution (1D tensor of size batchSize with meanTensor[i] >= 0 for each image in batch) * \param [in] stdDevTensor stdDev values for each image, which are used to compute the generalized Box-Mueller transforms in a gaussian distribution (1D tensor of size batchSize with stdDevTensor[i] >= 0 for each image in batch) + * \param [in] seed A user-defined seed value (single Rpp32u value) + * \param [in] roiGenericPtrSrc ROI data for each image in source tensor (tensor of batchSize RpptRoiGeneric values) + * \param [in] roiType ROI type used (RpptRoi3DType::XYZWHD or RpptRoi3DType::LTFRBB) * \param [in] rppHandle RPP HOST handle created with \ref rppCreateWithBatchSize() + * \return A \ref RppStatus enumeration. * \retval RPP_SUCCESS Successful completion. * \retval RPP_ERROR* Unsuccessful completion. */ diff --git a/include/rppt_tensor_morphological_operations.h b/include/rppt_tensor_morphological_operations.h index 126c4757a..1e070f1ad 100644 --- a/include/rppt_tensor_morphological_operations.h +++ b/include/rppt_tensor_morphological_operations.h @@ -67,6 +67,29 @@ extern "C" { RppStatus rppt_erode_gpu(RppPtr_t srcPtr, RpptDescPtr srcDescPtr, RppPtr_t dstPtr, RpptDescPtr dstDescPtr, Rpp32u kernelSize, RpptROIPtr roiTensorPtrSrc, RpptRoiType roiType, rppHandle_t rppHandle); #endif // GPU_SUPPORT +/*! \brief Dilate augmentation on HOST backend for a NCHW/NHWC layout tensor + * \details The dilate augmentation runs for a batch of RGB(3 channel) / greyscale(1 channel) images with an NHWC/NCHW tensor layout.
+ * - srcPtr depth ranges - Rpp8u (0 to 255), Rpp16f (0 to 1), Rpp32f (0 to 1), Rpp8s (-128 to 127). + * - dstPtr depth ranges - Will be same depth as srcPtr. + * \image html img150x150.png Sample Input + * \image html morphological_operations_dilate_kSize3_img150x150.png Sample 3x3 Output + * \image html morphological_operations_dilate_kSize5_img150x150.png Sample 5x5 Output + * \image html morphological_operations_dilate_kSize7_img150x150.png Sample 7x7 Output + * \image html morphological_operations_dilate_kSize9_img150x150.png Sample 9x9 Output + * \param [in] srcPtr source tensor in HOST memory + * \param [in] srcDescPtr source tensor descriptor (Restrictions - numDims = 4, offsetInBytes >= 0, dataType = U8/F16/F32/I8, layout = NCHW/NHWC, c = 1/3) + * \param [out] dstPtr destination tensor in HOST memory + * \param [in] dstDescPtr destination tensor descriptor (Restrictions - numDims = 4, offsetInBytes >= 0, dataType = U8/F16/F32/I8, layout = NCHW/NHWC, c = same as that of srcDescPtr) + * \param [in] kernelSize kernel size for box filter (a single Rpp32u odd number with kernelSize = 3/5/7/9 that applies to all images in the batch) + * \param [in] roiTensorPtrSrc ROI data in HOST memory, for each image in source tensor (2D tensor of size batchSize * 4, in either format - XYWH(xy.x, xy.y, roiWidth, roiHeight) or LTRB(lt.x, lt.y, rb.x, rb.y)) + * \param [in] roiType ROI type used (RpptRoiType::XYWH or RpptRoiType::LTRB) + * \param [in] rppHandle RPP HOST handle created with \ref rppCreateWithBatchSize() + * \return A \ref RppStatus enumeration. + * \retval RPP_SUCCESS Successful completion. + * \retval RPP_ERROR* Unsuccessful completion. + */ +RppStatus rppt_dilate_host(RppPtr_t srcPtr, RpptDescPtr srcDescPtr, RppPtr_t dstPtr, RpptDescPtr dstDescPtr, Rpp32u kernelSize, RpptROIPtr roiTensorPtrSrc, RpptRoiType roiType, rppHandle_t rppHandle); + #ifdef GPU_SUPPORT /*! \brief Dilate augmentation on HIP backend for a NCHW/NHWC layout tensor * \details The dilate augmentation runs for a batch of RGB(3 channel) / greyscale(1 channel) images with an NHWC/NCHW tensor layout.
diff --git a/src/include/cpu/rpp_cpu_common.hpp b/src/include/cpu/rpp_cpu_common.hpp index 973d728c6..e9e090886 100644 --- a/src/include/cpu/rpp_cpu_common.hpp +++ b/src/include/cpu/rpp_cpu_common.hpp @@ -510,24 +510,24 @@ inline int power_function(int a, int b) return product; } -inline void saturate_pixel(Rpp32f pixel, Rpp8u* dst) +inline void saturate_pixel(Rpp32f &pixel, Rpp8u* dst) { - *dst = RPPPIXELCHECK(pixel); + *dst = static_cast(RPPPIXELCHECK(std::nearbyintf(pixel))); } -inline void saturate_pixel(Rpp32f pixel, Rpp8s* dst) +inline void saturate_pixel(Rpp32f &pixel, Rpp8s* dst) { - *dst = (Rpp8s)RPPPIXELCHECKI8(pixel - 128); + *dst = static_cast(RPPPIXELCHECKI8(std::nearbyintf(pixel) - 128)); } -inline void saturate_pixel(Rpp32f pixel, Rpp32f* dst) +inline void saturate_pixel(Rpp32f &pixel, Rpp32f* dst) { - *dst = (Rpp32f)pixel; + *dst = RPPPIXELCHECKF32(pixel); } -inline void saturate_pixel(Rpp32f pixel, Rpp16f* dst) +inline void saturate_pixel(Rpp32f &pixel, Rpp16f* dst) { - *dst = (Rpp16f)pixel; + *dst = static_cast(RPPPIXELCHECKF32(pixel)); } template diff --git a/src/include/cpu/rpp_cpu_filter.hpp b/src/include/cpu/rpp_cpu_filter.hpp new file mode 100644 index 000000000..2b27b2efb --- /dev/null +++ b/src/include/cpu/rpp_cpu_filter.hpp @@ -0,0 +1,467 @@ +/* +MIT License + +Copyright (c) 2019 - 2024 Advanced Micro Devices, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#ifndef AMD_RPP_RPP_CPU_FILTER_HPP +#define AMD_RPP_RPP_CPU_FILTER_HPP + +#include "rpp_cpu_simd.hpp" + +// declare masks used for shuffle and permute operations used in filter functions +const __m128i xmm_pxMaskRotate0To1 = _mm_setr_epi8(2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 0, 1); +const __m128i xmm_pxMaskRotate0To3 = _mm_setr_epi8(4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 0, 1, 2, 3); +const __m128i xmm_pxMaskRotate0To5 = _mm_setr_epi8(6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 0, 1, 2, 3, 4, 5); +const __m128i xmm_pxMaskRotate0To7 = _mm_setr_epi8(8, 9, 10, 11, 12, 13, 14, 15, 0, 1, 2, 3, 4, 5, 6, 7); +const __m128i xmm_pxMaskRotate0To9 = _mm_setr_epi8(10, 11, 12, 13, 14, 15, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9); +const __m128i xmm_pxMaskRotate0To11 = _mm_setr_epi8(12, 13, 14, 15, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11); +const __m128i xmm_pxMaskRotate0To13 = _mm_setr_epi8(14, 15, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13); +const __m256i avx_pxMaskRotate0To1 = _mm256_setr_epi32(1, 2, 3, 4, 5, 6, 7, 0); +const __m256i avx_pxMaskRotate0To2 = _mm256_setr_epi32(2, 3, 4, 5, 6, 7, 0, 1); +const __m256i avx_pxMaskRotate0To3 = _mm256_setr_epi32(3, 4, 5, 6, 7, 0, 1, 2); +const __m256i avx_pxMaskRotate0To4 = _mm256_setr_epi32(4, 5, 6, 7, 0, 1, 2, 3); +const __m256i avx_pxMaskRotate0To5 = _mm256_setr_epi32(5, 6, 7, 0, 1, 2, 3, 4); +const __m256i avx_pxMaskRotate0To6 = _mm256_setr_epi32(6, 7, 0, 1, 2, 3, 4, 5); +const __m256i avx_pxMaskRotate0To7 = _mm256_setr_epi32(7, 0, 1, 2, 3, 4, 5, 6); + +// increment the kernel size number of row pointers with increment value +template +inline void increment_row_ptrs(T **srcPtrTemp, Rpp32u kernelSize, Rpp32s increment) +{ + for (int i = 0; i < kernelSize; i++) + srcPtrTemp[i] += increment; +} + +// get the kernel loop limit based on index +inline void get_kernel_loop_limit(Rpp32s &index, Rpp32s &loopLimit, Rpp32u &padLength, Rpp32u &unpaddedLength) +{ + if ((index < padLength) || (index >= unpaddedLength)) + { + Rpp32u factor = (index < padLength) ? (index - padLength) : (unpaddedLength - 1 - index); + loopLimit += factor; + } +} + +// extract 4 SSE registers from 2 AVX registers +inline void extract_4sse_registers(__m256i *pxRowHalf, __m128i *px128) +{ + px128[0] = _mm256_castsi256_si128(pxRowHalf[0]); + px128[1] = _mm256_castsi256_si128(pxRowHalf[1]); + px128[2] = _mm256_extracti128_si256(pxRowHalf[0], 1); + px128[3] = _mm256_extracti128_si256(pxRowHalf[1], 1); +} + +// extract 3 SSE registers from 2 AVX registers +inline void extract_3sse_registers(__m256i *pxRowHalf, __m128i *px128) +{ + px128[0] = _mm256_castsi256_si128(pxRowHalf[0]); + px128[1] = _mm256_castsi256_si128(pxRowHalf[1]); + px128[2] = _mm256_extracti128_si256(pxRowHalf[0], 1); +} + +// -------------------- U8/I8 bitdepth compute functions for kernel size (3/5/7/9) -------------------- + +// perform required blend shuffle add operations for 3x3 kernel size +template +inline void blend_shuffle_max_3x3_host(__m128i *px128, __m128i *pxMask, Rpp32u *index) +{ + /* For PLN inputs | For PKD inputs + px128[0] - [X01|X02|X03|X04|X05|X06|X07|X08], px128[1] - [X09|X10|X11|X12|X13|X14|X15|X16]| px128[0] - [R01|G01|B01|R02|G02|B02|R03|G03], px128[1] - [B03|R04|G04|B04|R05|G05|B05|R06] + pxTemp[0] - [X02|X03|X04|X05|X06|X07|X08|X09] (blend with mask [0000 0001] and shuffle) | pxTemp[0] - [R02|G02|B02|R03|G03|B03|R04|G04] (blend with mask [0000 0111] and shuffle) + pxTemp[1] - [X03|X04|X05|X06|X07|X08|X09|X10] (blend with mask [0000 0011] and shuffle) | pxTemp[1] - [R03|G03|B03|R04|G04|B04|R05|G05] (blend with mask [0011 1111] and shuffle) */ + __m128i pxTemp[2]; + pxTemp[0] = _mm_shuffle_epi8(_mm_blend_epi16(px128[index[0]], px128[index[0] + 1], blendMask1), pxMask[0]); + pxTemp[1] = _mm_shuffle_epi8(_mm_blend_epi16(px128[index[1]], px128[index[1] + 1], blendMask2), pxMask[1]); + px128[0] = _mm_max_epi16(_mm_max_epi16(px128[0], pxTemp[0]), pxTemp[1]); +} + +// perform required blend shuffle add operations for 5x5 kernel size +template +inline void blend_shuffle_max_5x5_host(__m128i *px128, __m128i *pxMask, Rpp32u *index) +{ + /* For PLN inputs | For PKD inputs + px128[0] - [X01|X02|X03|X04|X05|X06|X07|X08], px128[1] - [X09|X10|X11|X12|X13|X14|X15|X16]| px128[0] - [R01|G01|B01|R02|G02|B02|R03|G03], px128[1] - [B03|R04|G04|B04|R05|G05|B05|R06] + pxTemp[0] - [X02|X03|X04|X05|X06|X07|X08|X09] (blend with mask [0000 0001] and shuffle) | px128[2] - [G06|B06|R07|G07|B07|R08|G08|B08] + pxTemp[1] - [X03|X04|X05|X06|X07|X08|X09|X10] (blend with mask [0000 0011] and shuffle) | pxTemp[0] - [R02|G02|B02|R03|G03|B03|R04|G04] (blend with mask [0000 0111] and shuffle) + pxTemp[2] - [X04|X05|X06|X07|X08|X09|X10|X11] (blend with mask [0000 0111] and shuffle) | pxTemp[1] - [R03|G03|B03|R04|G04|B04|R05|G05] (blend with mask [0011 1111] and shuffle) + pxTemp[3] - [X05|X06|X07|X08|X09|X10|X11|X12] (blend with mask [0000 1111] and shuffle) | pxTemp[2] - [R04|G04|B04|R05|G05|B05|R06|G06] (blend with mask [0000 0001] and shuffle) + | pxTemp[3] - [R05|G05|B05|R06|G06|B06|R07|G07] (blend with mask [0000 1111] and shuffle) */ + __m128i pxTemp[4]; + pxTemp[0] = _mm_shuffle_epi8(_mm_blend_epi16(px128[index[0]], px128[index[0] + 1], blendMask1), pxMask[0]); + pxTemp[1] = _mm_shuffle_epi8(_mm_blend_epi16(px128[index[1]], px128[index[1] + 1], blendMask2), pxMask[1]); + pxTemp[2] = _mm_shuffle_epi8(_mm_blend_epi16(px128[index[2]], px128[index[2] + 1], blendMask3), pxMask[2]); + pxTemp[3] = _mm_shuffle_epi8(_mm_blend_epi16(px128[index[3]], px128[index[3] + 1], blendMask4), pxMask[3]); + px128[0] = _mm_max_epi16(_mm_max_epi16(_mm_max_epi16(_mm_max_epi16(px128[0], pxTemp[0]), pxTemp[1]), pxTemp[2]), pxTemp[3]); +} + +// perform required blend shuffle add operations for 7x7 kernel size +template +inline void blend_shuffle_max_7x7_host(__m128i *px128, __m128i *pxMask, Rpp32u *index) +{ + /* For PLN inputs | For PKD inputs + px128[0] - [X01|X02|X03|X04|X05|X06|X07|X08], px128[1] - [X09|X10|X11|X12|X13|X14|X15|X16]| px128[0] - [R01|G01|B01|R02|G02|B02|R03|G03], px128[1] - [B03|R04|G04|B04|R05|G05|B05|R06], + pxTemp[0] - [X02|X03|X04|X05|X06|X07|X08|X09] (blend with mask [0000 0001] and shuffle) | px128[2] - [G06|B06|R07|G07|B07|R08|G08|B08], px128[3] - [R09|G09|B09|R10|G10|B10|R11|G11] + pxTemp[1] - [X03|X04|X05|X06|X07|X08|X09|X10] (blend with mask [0000 0011] and shuffle) | pxTemp[0] - [R02|G02|B02|R03|G03|B03|R04|G04] (blend with mask [0000 0111] and shuffle) + pxTemp[2] - [X04|X05|X06|X07|X08|X09|X10|X11] (blend with mask [0000 0111] and shuffle) | pxTemp[1] - [R03|G03|B03|R04|G04|B04|R05|G05] (blend with mask [0011 1111] and shuffle) + pxTemp[3] - [X05|X06|X07|X08|X09|X10|X11|X12] (blend with mask [0000 1111] and shuffle) | pxTemp[2] - [R04|G04|B04|R05|G05|B05|R06|G06] (blend with mask [0000 0001] and shuffle) + pxTemp[4] - [X06|X07|X08|X09|X10|X11|X12|X13] (blend with mask [0001 1111] and shuffle) | pxTemp[3] - [R05|G05|B05|R06|G06|B06|R07|G07] (blend with mask [0000 1111] and shuffle) + pxTemp[5] - [X07|X08|X09|X10|X11|X12|X13|X14] (blend with mask [0011 1111] and shuffle) | pxTemp[4] - [R06|G06|B06|R07|G07|B07|R08|G08] (blend with mask [0111 1111] and shuffle) + | pxTemp[5] - [R07|G07|B07|R08|G08|B08|R09|G09] (blend with mask [0000 0011] and shuffle) */ + __m128i pxTemp[6]; + pxTemp[0] = _mm_shuffle_epi8(_mm_blend_epi16(px128[index[0]], px128[index[0] + 1], blendMask1), pxMask[0]); + pxTemp[1] = _mm_shuffle_epi8(_mm_blend_epi16(px128[index[1]], px128[index[1] + 1], blendMask2), pxMask[1]); + pxTemp[2] = _mm_shuffle_epi8(_mm_blend_epi16(px128[index[2]], px128[index[2] + 1], blendMask3), pxMask[2]); + pxTemp[3] = _mm_shuffle_epi8(_mm_blend_epi16(px128[index[3]], px128[index[3] + 1], blendMask4), pxMask[3]); + pxTemp[4] = _mm_shuffle_epi8(_mm_blend_epi16(px128[index[4]], px128[index[4] + 1], blendMask5), pxMask[4]); + pxTemp[5] = _mm_shuffle_epi8(_mm_blend_epi16(px128[index[5]], px128[index[5] + 1], blendMask6), pxMask[5]); + px128[0] = _mm_max_epi16(_mm_max_epi16(_mm_max_epi16(px128[0], pxTemp[0]), pxTemp[1]), pxTemp[2]); + px128[0] = _mm_max_epi16(_mm_max_epi16(_mm_max_epi16(px128[0], pxTemp[3]), pxTemp[4]), pxTemp[5]); +} + +// perform required blend shuffle add operations for 9x9 kernel size +template +inline void blend_shuffle_max_9x9_host(__m128i *px128, __m128i *pxMask, Rpp32u *index) +{ + /* For PLN inputs | For PKD inputs + px128[0] - [X01|X02|X03|X04|X05|X06|X07|X08], px128[1] - [X09|X10|X11|X12|X13|X14|X15|X16]| px128[0] - [R01|G01|B01|R02|G02|B02|R03|G03], px128[1] - [B03|R04|G04|B04|R05|G05|B05|R06], + pxTemp[0] - [X02|X03|X04|X05|X06|X07|X08|X09] (blend with mask [0000 0001] and shuffle) | px128[2] - [G06|B06|R07|G07|B07|R08|G08|B08], px128[3] - [R09|G09|B09|R10|G10|B10|R11|G11] + pxTemp[1] - [X03|X04|X05|X06|X07|X08|X09|X10] (blend with mask [0000 0011] and shuffle) | pxTemp[0] - [R02|G02|B02|R03|G03|B03|R04|G04] (blend with mask [0000 0111] and shuffle) + pxTemp[2] - [X04|X05|X06|X07|X08|X09|X10|X11] (blend with mask [0000 0111] and shuffle) | pxTemp[1] - [R03|G03|B03|R04|G04|B04|R05|G05] (blend with mask [0011 1111] and shuffle) + pxTemp[3] - [X05|X06|X07|X08|X09|X10|X11|X12] (blend with mask [0000 1111] and shuffle) | pxTemp[2] - [R04|G04|B04|R05|G05|B05|R06|G06] (blend with mask [0000 0001] and shuffle) + pxTemp[4] - [X06|X07|X08|X09|X10|X11|X12|X13] (blend with mask [0001 1111] and shuffle) | pxTemp[3] - [R05|G05|B05|R06|G06|B06|R07|G07] (blend with mask [0000 1111] and shuffle) + pxTemp[5] - [X07|X08|X09|X10|X11|X12|X13|X14] (blend with mask [0011 1111] and shuffle) | pxTemp[4] - [R06|G06|B06|R07|G07|B07|R08|G08] (blend with mask [0111 1111] and shuffle) + pxTemp[6] - [X08|X09|X10|X11|X12|X13|X14|X15] (blend with mask [0111 1111] and shuffle) | pxTemp[5] - [R07|G07|B07|R08|G08|B08|R09|G09] (blend with mask [0000 0011] and shuffle) + | pxTemp[6] - [R08|G08|B08|R09|G09|B09|R10|G10] (blend with mask [0001 1111] and shuffle) */ + __m128i pxTemp[7]; + pxTemp[0] = _mm_shuffle_epi8(_mm_blend_epi16(px128[index[0]], px128[index[0] + 1], blendMask1), pxMask[0]); + pxTemp[1] = _mm_shuffle_epi8(_mm_blend_epi16(px128[index[1]], px128[index[1] + 1], blendMask2), pxMask[1]); + pxTemp[2] = _mm_shuffle_epi8(_mm_blend_epi16(px128[index[2]], px128[index[2] + 1], blendMask3), pxMask[2]); + pxTemp[3] = _mm_shuffle_epi8(_mm_blend_epi16(px128[index[3]], px128[index[3] + 1], blendMask4), pxMask[3]); + pxTemp[4] = _mm_shuffle_epi8(_mm_blend_epi16(px128[index[4]], px128[index[4] + 1], blendMask5), pxMask[4]); + pxTemp[5] = _mm_shuffle_epi8(_mm_blend_epi16(px128[index[5]], px128[index[5] + 1], blendMask6), pxMask[5]); + pxTemp[6] = _mm_shuffle_epi8(_mm_blend_epi16(px128[index[6]], px128[index[6] + 1], blendMask7), pxMask[6]); + px128[0] = _mm_max_epi16(_mm_max_epi16(_mm_max_epi16(_mm_max_epi16(px128[0], pxTemp[0]), pxTemp[1]), pxTemp[2]), pxTemp[3]); + px128[0] = _mm_max_epi16(_mm_max_epi16(_mm_max_epi16(_mm_max_epi16(px128[0], pxTemp[4]), pxTemp[5]), pxTemp[6]), px128[index[6] + 1]); +} + +// -------------------- F32/F16 bitdepth compute functions for kernel size (3/5/7/9) -------------------- + +// perform required blend permute add multiplication operations for 3x3 kernel size +template +inline void blend_permute_max_3x3_host(__m256 *pSrc, __m256 *pDst, __m256 pConvolutionFactor, __m256i *pxMask, Rpp32u *index) +{ + /* For PLN inputs | For PKD inputs + pSrc[0] - [X01|X02|X03|X04|X05|X06|X07|X08], pSrc[1] - [X09|X10|X11|X12|X13|X14|X15|X16]| pSrc[0] - [R01|G01|B01|R02|G02|B02|R03|G03], pSrc[1] - [B03|R04|G04|B04|R05|G05|B05|R06] + [X02|X03|X04|X05|X06|X07|X08|X09] (blend with mask [0000 0001] and permute) | [R02|G02|B02|R03|G03|B03|R04|G04] (blend with mask [0000 0111] and permute) + [X03|X04|X05|X06|X07|X08|X09|X10] (blend with mask [0000 0011] and permute) | [R03|G03|B03|R04|G04|B04|R05|G05] (blend with mask [0011 1111] and permute) */ + pDst[0] = _mm256_max_ps(pSrc[0], _mm256_permutevar8x32_ps(_mm256_blend_ps(pSrc[index[0]], pSrc[index[0] + 1], blendMask1), pxMask[0])); + pDst[0] = _mm256_max_ps(pDst[0], _mm256_permutevar8x32_ps(_mm256_blend_ps(pSrc[index[1]], pSrc[index[1] + 1], blendMask2), pxMask[1])); +} + +// perform required blend permute add multiplication operations for 5x5 kernel size +template +inline void blend_permute_max_5x5_host(__m256 *pSrc, __m256 *pDst, __m256 pConvolutionFactor, __m256i *pxMask, Rpp32u *index) +{ + /* For PLN inputs | For PKD inputs + pSrc[0] - [X01|X02|X03|X04|X05|X06|X07|X08], pSrc[1] - [X09|X10|X11|X12|X13|X14|X15|X16]| pSrc[0] - [R01|G01|B01|R02|G02|B02|R03|G03], pSrc[1] - [B03|R04|G04|B04|R05|G05|B05|R06] + [X02|X03|X04|X05|X06|X07|X08|X09] (blend with mask [0000 0001] and permute) | pSrc[2] - [G06|B06|R07|G07|B07|R08|G08|B08] + [X03|X04|X05|X06|X07|X08|X09|X10] (blend with mask [0000 0011] and permute) | [R02|G02|B02|R03|G03|B03|R04|G04] (blend with mask [0000 0111] and permute) + [X04|X05|X06|X07|X08|X09|X10|X11] (blend with mask [0000 0111] and permute) | [R03|G03|B03|R04|G04|B04|R05|G05] (blend with mask [0011 1111] and permute) + [X05|X06|X07|X08|X09|X10|X11|X12] (blend with mask [0000 1111] and permute) | [R04|G04|B04|R05|G05|B05|R06|G06] (blend with mask [0000 0001] and permute) + | [R05|G05|B05|R06|G06|B06|R07|G07] (blend with mask [0000 1111] and permute) */ + pDst[0] = _mm256_max_ps(pSrc[0], _mm256_permutevar8x32_ps(_mm256_blend_ps(pSrc[index[0]], pSrc[index[0] + 1], blendMask1), pxMask[0])); // blend with mask [0000 0001] and permute - [X02|X03|X04|X05|X06|X07|X08|X09] + pDst[0] = _mm256_max_ps(pDst[0], _mm256_permutevar8x32_ps(_mm256_blend_ps(pSrc[index[1]], pSrc[index[1] + 1], blendMask2), pxMask[1])); // blend with mask [0000 0011] and permute - [X03|X04|X05|X06|X07|X08|X09|X10] + pDst[0] = _mm256_max_ps(pDst[0], _mm256_permutevar8x32_ps(_mm256_blend_ps(pSrc[index[2]], pSrc[index[2] + 1], blendMask3), pxMask[2])); // blend with mask [0000 0111] and permute - [X04|X05|X06|X07|X08|X09|X10|X11] + pDst[0] = _mm256_max_ps(pDst[0], _mm256_permutevar8x32_ps(_mm256_blend_ps(pSrc[index[3]], pSrc[index[3] + 1], blendMask4), pxMask[3])); // blend with mask [0000 1111] and permute - [X05|X06|X07|X08|X09|X10|X11|X12] +} + +// perform required blend permute add multiplication operations for 7x7 kernel size +template +inline void blend_permute_max_7x7_host(__m256 *pSrc, __m256 *pDst, __m256 pConvolutionFactor, __m256i *pxMask, Rpp32u *index) +{ + /* For PLN inputs | For PKD inputs + pSrc[0] - [X01|X02|X03|X04|X05|X06|X07|X08], pSrc[1] - [X09|X10|X11|X12|X13|X14|X15|X16]| pSrc[0] - [R01|G01|B01|R02|G02|B02|R03|G03], pSrc[1] - [B03|R04|G04|B04|R05|G05|B05|R06], + [X02|X03|X04|X05|X06|X07|X08|X09] (blend with mask [0000 0001] and permute) | pSrc[2] - [G06|B06|R07|G07|B07|R08|G08|B08], pSrc[3] - [R09|G09|B09|R10|G10|B10|R11|G11] + [X03|X04|X05|X06|X07|X08|X09|X10] (blend with mask [0000 0011] and permute) | [R02|G02|B02|R03|G03|B03|R04|G04] (blend with mask [0000 0111] and permute) + [X04|X05|X06|X07|X08|X09|X10|X11] (blend with mask [0000 0111] and permute) | [R03|G03|B03|R04|G04|B04|R05|G05] (blend with mask [0011 1111] and permute) + [X05|X06|X07|X08|X09|X10|X11|X12] (blend with mask [0000 1111] and permute) | [R04|G04|B04|R05|G05|B05|R06|G06] (blend with mask [0000 0001] and permute) + [X06|X07|X08|X09|X10|X11|X12|X13] (blend with mask [0001 1111] and permute) | [R05|G05|B05|R06|G06|B06|R07|G07] (blend with mask [0000 1111] and permute) + [X07|X08|X09|X10|X11|X12|X13|X14] (blend with mask [0011 1111] and permute) | [R06|G06|B06|R07|G07|B07|R08|G08] (blend with mask [0111 1111] and permute) + | [R07|G07|B07|R08|G08|B08|R09|G09] (blend with mask [0000 0011] and permute) */ + pDst[0] = _mm256_max_ps(pSrc[0], _mm256_permutevar8x32_ps(_mm256_blend_ps(pSrc[index[0]], pSrc[index[0] + 1], blendMask1), pxMask[0])); // blend with mask [0000 0001] and permute - [X02|X03|X04|X05|X06|X07|X08|X09] + pDst[0] = _mm256_max_ps(pDst[0], _mm256_permutevar8x32_ps(_mm256_blend_ps(pSrc[index[1]], pSrc[index[1] + 1], blendMask2), pxMask[1])); // blend with mask [0000 0011] and permute - [X03|X04|X05|X06|X07|X08|X09|X10] + pDst[0] = _mm256_max_ps(pDst[0], _mm256_permutevar8x32_ps(_mm256_blend_ps(pSrc[index[2]], pSrc[index[2] + 1], blendMask3), pxMask[2])); // blend with mask [0000 0111] and permute - [X04|X05|X06|X07|X08|X09|X10|X11] + pDst[0] = _mm256_max_ps(pDst[0], _mm256_permutevar8x32_ps(_mm256_blend_ps(pSrc[index[3]], pSrc[index[3] + 1], blendMask4), pxMask[3])); // blend with mask [0000 1111] and permute - [X05|X06|X07|X08|X09|X10|X11|X12] + pDst[0] = _mm256_max_ps(pDst[0], _mm256_permutevar8x32_ps(_mm256_blend_ps(pSrc[index[4]], pSrc[index[4] + 1], blendMask5), pxMask[4])); // blend with mask [0001 1111] and permute - [X06|X07|X08|X09|X10|X11|X12|X13] + pDst[0] = _mm256_max_ps(pDst[0], _mm256_permutevar8x32_ps(_mm256_blend_ps(pSrc[index[5]], pSrc[index[5] + 1], blendMask6), pxMask[5])); // blend with mask [0011 1111] and permute - [X07|X08|X09|X10|X11|X12|X13|X14] +} + +// perform required blend permute add multiplication operations for 9x9 kernel size +template +inline void blend_permute_max_9x9_host(__m256 *pSrc, __m256 *pDst, __m256 pConvolutionFactor, __m256i *pxMask, Rpp32u *index) +{ + /* For PLN inputs | For PKD inputs + pSrc[0] - [X01|X02|X03|X04|X05|X06|X07|X08], pSrc[1] - [X09|X10|X11|X12|X13|X14|X15|X16]| pSrc[0] - [R01|G01|B01|R02|G02|B02|R03|G03], pSrc[1] - [B03|R04|G04|B04|R05|G05|B05|R06], + [X02|X03|X04|X05|X06|X07|X08|X09] (blend with mask [0000 0001] and permute) pSrc[2] - [G06|B06|R07|G07|B07|R08|G08|B08], pSrc[3] - [R09|G09|B09|R10|G10|B10|R11|G11] + [X03|X04|X05|X06|X07|X08|X09|X10] (blend with mask [0000 0011] and permute) | [R02|G02|B02|R03|G03|B03|R04|G04] (blend with mask [0000 0111] and permute) + [X04|X05|X06|X07|X08|X09|X10|X11] (blend with mask [0000 0111] and permute) | [R03|G03|B03|R04|G04|B04|R05|G05] (blend with mask [0011 1111] and permute) + [X05|X06|X07|X08|X09|X10|X11|X12] (blend with mask [0000 1111] and permute) | [R04|G04|B04|R05|G05|B05|R06|G06] (blend with mask [0000 0001] and permute) + [X06|X07|X08|X09|X10|X11|X12|X13] (blend with mask [0001 1111] and permute) | [R05|G05|B05|R06|G06|B06|R07|G07] (blend with mask [0000 1111] and permute) + [X07|X08|X09|X10|X11|X12|X13|X14] (blend with mask [0011 1111] and permute) | [R06|G06|B06|R07|G07|B07|R08|G08] (blend with mask [0111 1111] and permute) + [X08|X09|X10|X11|X12|X13|X14|X15] (blend with mask [0111 1111] and permute) | [R07|G07|B07|R08|G08|B08|R09|G09] (blend with mask [0000 0011] and permute) + | [R08|G08|B08|R09|G09|B09|R10|G10] (blend with mask [0001 1111] and permute) + */ + pDst[0] = _mm256_max_ps(pSrc[0], _mm256_permutevar8x32_ps(_mm256_blend_ps(pSrc[index[0]], pSrc[index[0] + 1], blendMask1), pxMask[0])); // blend with mask [0000 0001] and permute - [X02|X03|X04|X05|X06|X07|X08|X09] + pDst[0] = _mm256_max_ps(pDst[0], _mm256_permutevar8x32_ps(_mm256_blend_ps(pSrc[index[1]], pSrc[index[1] + 1], blendMask2), pxMask[1])); // blend with mask [0000 0011] and permute - [X03|X04|X05|X06|X07|X08|X09|X10] + pDst[0] = _mm256_max_ps(pDst[0], _mm256_permutevar8x32_ps(_mm256_blend_ps(pSrc[index[2]], pSrc[index[2] + 1], blendMask3), pxMask[2])); // blend with mask [0000 0111] and permute - [X04|X05|X06|X07|X08|X09|X10|X11] + pDst[0] = _mm256_max_ps(pDst[0], _mm256_permutevar8x32_ps(_mm256_blend_ps(pSrc[index[3]], pSrc[index[3] + 1], blendMask4), pxMask[3])); // blend with mask [0000 1111] and permute - [X05|X06|X07|X08|X09|X10|X11|X12] + pDst[0] = _mm256_max_ps(pDst[0], _mm256_permutevar8x32_ps(_mm256_blend_ps(pSrc[index[4]], pSrc[index[4] + 1], blendMask5), pxMask[4])); // blend with mask [0001 1111] and permute - [X06|X07|X08|X09|X10|X11|X12|X13] + pDst[0] = _mm256_max_ps(pDst[0], _mm256_permutevar8x32_ps(_mm256_blend_ps(pSrc[index[5]], pSrc[index[5] + 1], blendMask6), pxMask[5])); // blend with mask [0011 1111] and permute - [X07|X08|X09|X10|X11|X12|X13|X14] + pDst[0] = _mm256_max_ps(pDst[0], _mm256_permutevar8x32_ps(_mm256_blend_ps(pSrc[index[6]], pSrc[index[6] + 1], blendMask7), pxMask[6])); // blend with mask [0111 1111] and permute - [X08|X09|X10|X11|X12|X13|X14|X15] + pDst[0] = _mm256_max_ps(pDst[0], pSrc[index[6] + 1]); +} + +// -------------------- Filter load functions for U8 bitdepth -------------------- + +// load function for 3x3 kernel size +inline void rpp_load_dilate_char_3x3_host(__m256i *pxRow, Rpp8u **srcPtrTemp, Rpp32s rowKernelLoopLimit) +{ + // irrespective of row location, we need to load 2 rows for 3x3 kernel + pxRow[0] = _mm256_loadu_si256((__m256i *)srcPtrTemp[0]); + pxRow[1] = _mm256_loadu_si256((__m256i *)srcPtrTemp[1]); + if (rowKernelLoopLimit == 3) + pxRow[2] = _mm256_loadu_si256((__m256i *)srcPtrTemp[2]); + else + pxRow[2] = avx_px0; +} + +// load function for 5x5 kernel size +inline void rpp_load_dilate_char_5x5_host(__m256i *pxRow, Rpp8u **srcPtrTemp, Rpp32s rowKernelLoopLimit) +{ + // irrespective of row location, we need to load 3 rows for 5x5 kernel + pxRow[0] = _mm256_loadu_si256((__m256i *)srcPtrTemp[0]); + pxRow[1] = _mm256_loadu_si256((__m256i *)srcPtrTemp[1]); + pxRow[2] = _mm256_loadu_si256((__m256i *)srcPtrTemp[2]); + for (int k = 3; k < rowKernelLoopLimit; k++) + pxRow[k] = _mm256_loadu_si256((__m256i *)srcPtrTemp[k]); + for (int k = rowKernelLoopLimit; k < 5; k++) + pxRow[k] = avx_px0; +} + +// load function for 7x7 kernel size +inline void rpp_load_dilate_char_7x7_host(__m256i *pxRow, Rpp8u **srcPtrTemp, Rpp32s rowKernelLoopLimit) +{ + // irrespective of row location, we need to load 4 rows for 7x7 kernel + pxRow[0] = _mm256_loadu_si256((__m256i *)srcPtrTemp[0]); + pxRow[1] = _mm256_loadu_si256((__m256i *)srcPtrTemp[1]); + pxRow[2] = _mm256_loadu_si256((__m256i *)srcPtrTemp[2]); + pxRow[3] = _mm256_loadu_si256((__m256i *)srcPtrTemp[3]); + for (int k = 4; k < rowKernelLoopLimit; k++) + pxRow[k] = _mm256_loadu_si256((__m256i *)srcPtrTemp[k]); + for (int k = rowKernelLoopLimit; k < 7; k++) + pxRow[k] = avx_px0; +} + +// load function for 9x9 kernel size +inline void rpp_load_dilate_char_9x9_host(__m256i *pxRow, Rpp8u **srcPtrTemp, Rpp32s rowKernelLoopLimit) +{ + // irrespective of row location, we need to load 5 rows for 9x9 kernel + pxRow[0] = _mm256_loadu_si256((__m256i *)srcPtrTemp[0]); + pxRow[1] = _mm256_loadu_si256((__m256i *)srcPtrTemp[1]); + pxRow[2] = _mm256_loadu_si256((__m256i *)srcPtrTemp[2]); + pxRow[3] = _mm256_loadu_si256((__m256i *)srcPtrTemp[3]); + pxRow[4] = _mm256_loadu_si256((__m256i *)srcPtrTemp[4]); + for (int k = 5; k < rowKernelLoopLimit; k++) + pxRow[k] = _mm256_loadu_si256((__m256i *)srcPtrTemp[k]); + for (int k = rowKernelLoopLimit; k < 9; k++) + pxRow[k] = avx_px0; +} + +// -------------------- Filter load functions for I8 bitdepth -------------------- + +// load function for 3x3 kernel size +inline void rpp_load_dilate_char_3x3_host(__m256i *pxRow, Rpp8s **srcPtrTemp, Rpp32s rowKernelLoopLimit) +{ + // irrespective of row location, we need to load 2 rows for 3x3 kernel + pxRow[0] = _mm256_add_epi8(avx_pxConvertI8, _mm256_loadu_si256((__m256i *)srcPtrTemp[0])); + pxRow[1] = _mm256_add_epi8(avx_pxConvertI8, _mm256_loadu_si256((__m256i *)srcPtrTemp[1])); + if (rowKernelLoopLimit == 3) + pxRow[2] = _mm256_add_epi8(avx_pxConvertI8, _mm256_loadu_si256((__m256i *)srcPtrTemp[2])); + else + pxRow[2] = avx_px0; +} + +// load function for 5x5 kernel size +inline void rpp_load_dilate_char_5x5_host(__m256i *pxRow, Rpp8s **srcPtrTemp, Rpp32s rowKernelLoopLimit) +{ + // irrespective of row location, we need to load 3 rows for 5x5 kernel + pxRow[0] = _mm256_add_epi8(avx_pxConvertI8, _mm256_loadu_si256((__m256i *)srcPtrTemp[0])); + pxRow[1] = _mm256_add_epi8(avx_pxConvertI8, _mm256_loadu_si256((__m256i *)srcPtrTemp[1])); + pxRow[2] = _mm256_add_epi8(avx_pxConvertI8, _mm256_loadu_si256((__m256i *)srcPtrTemp[2])); + for (int k = 3; k < rowKernelLoopLimit; k++) + pxRow[k] = _mm256_add_epi8(avx_pxConvertI8, _mm256_loadu_si256((__m256i *)srcPtrTemp[k])); + for (int k = rowKernelLoopLimit; k < 5; k++) + pxRow[k] = avx_px0; +} + +// load function for 7x7 kernel size +inline void rpp_load_dilate_char_7x7_host(__m256i *pxRow, Rpp8s **srcPtrTemp, Rpp32s rowKernelLoopLimit) +{ + // irrespective of row location, we need to load 4 rows for 7x7 kernel + pxRow[0] = _mm256_add_epi8(avx_pxConvertI8, _mm256_loadu_si256((__m256i *)srcPtrTemp[0])); + pxRow[1] = _mm256_add_epi8(avx_pxConvertI8, _mm256_loadu_si256((__m256i *)srcPtrTemp[1])); + pxRow[2] = _mm256_add_epi8(avx_pxConvertI8, _mm256_loadu_si256((__m256i *)srcPtrTemp[2])); + pxRow[3] = _mm256_add_epi8(avx_pxConvertI8, _mm256_loadu_si256((__m256i *)srcPtrTemp[3])); + for (int k = 4; k < rowKernelLoopLimit; k++) + pxRow[k] = _mm256_add_epi8(avx_pxConvertI8, _mm256_loadu_si256((__m256i *)srcPtrTemp[k])); + for (int k = rowKernelLoopLimit; k < 7; k++) + pxRow[k] = avx_px0; +} + +// load function for 9x9 kernel size +inline void rpp_load_dilate_char_9x9_host(__m256i *pxRow, Rpp8s **srcPtrTemp, Rpp32s rowKernelLoopLimit) +{ + // irrespective of row location, we need to load 5 rows for 9x9 kernel + pxRow[0] = _mm256_add_epi8(avx_pxConvertI8, _mm256_loadu_si256((__m256i *)srcPtrTemp[0])); + pxRow[1] = _mm256_add_epi8(avx_pxConvertI8, _mm256_loadu_si256((__m256i *)srcPtrTemp[1])); + pxRow[2] = _mm256_add_epi8(avx_pxConvertI8, _mm256_loadu_si256((__m256i *)srcPtrTemp[2])); + pxRow[3] = _mm256_add_epi8(avx_pxConvertI8, _mm256_loadu_si256((__m256i *)srcPtrTemp[3])); + pxRow[4] = _mm256_add_epi8(avx_pxConvertI8, _mm256_loadu_si256((__m256i *)srcPtrTemp[4])); + for (int k = 5; k < rowKernelLoopLimit; k++) + pxRow[k] = _mm256_add_epi8(avx_pxConvertI8, _mm256_loadu_si256((__m256i *)srcPtrTemp[k])); + for (int k = rowKernelLoopLimit; k < 9; k++) + pxRow[k] = avx_px0; +} + +// -------------------- Filter load functions for F32 bitdepth -------------------- + +// load function for 3x3 kernel size +inline void rpp_load_dilate_float_3x3_host(__m256 *pRow, Rpp32f **srcPtrTemp, Rpp32s rowKernelLoopLimit) +{ + // irrespective of row location, we need to load 2 rows for 3x3 kernel + pRow[0] = _mm256_loadu_ps(srcPtrTemp[0]); + pRow[1] = _mm256_loadu_ps(srcPtrTemp[1]); + if (rowKernelLoopLimit == 3) + pRow[2] = _mm256_loadu_ps(srcPtrTemp[2]); + else + pRow[2] = avx_p0; +} + +// load function for 5x5 kernel size +inline void rpp_load_dilate_float_5x5_host(__m256 *pRow, Rpp32f **srcPtrTemp, Rpp32s rowKernelLoopLimit) +{ + // irrespective of row location, we need to load 3 rows for 5x5 kernel + pRow[0] = _mm256_loadu_ps(srcPtrTemp[0]); + pRow[1] = _mm256_loadu_ps(srcPtrTemp[1]); + pRow[2] = _mm256_loadu_ps(srcPtrTemp[2]); + for (int k = 3; k < rowKernelLoopLimit; k++) + pRow[k] = _mm256_loadu_ps(srcPtrTemp[k]); + for (int k = rowKernelLoopLimit; k < 5; k++) + pRow[k] = avx_p0; +} + +// load function for 7x7 kernel size +inline void rpp_load_dilate_float_7x7_host(__m256 *pRow, Rpp32f **srcPtrTemp, Rpp32s rowKernelLoopLimit) +{ + // irrespective of row location, we need to load 4 rows for 7x7 kernel + pRow[0] = _mm256_loadu_ps(srcPtrTemp[0]); + pRow[1] = _mm256_loadu_ps(srcPtrTemp[1]); + pRow[2] = _mm256_loadu_ps(srcPtrTemp[2]); + pRow[3] = _mm256_loadu_ps(srcPtrTemp[3]); + for (int k = 4; k < rowKernelLoopLimit; k++) + pRow[k] = _mm256_loadu_ps(srcPtrTemp[k]); + for (int k = rowKernelLoopLimit; k < 7; k++) + pRow[k] = avx_p0; +} + +// load function for 9x9 kernel size +inline void rpp_load_dilate_float_9x9_host(__m256 *pRow, Rpp32f **srcPtrTemp, Rpp32s rowKernelLoopLimit) +{ + // irrespective of row location, we need to load 5 rows for 9x9 kernel + pRow[0] = _mm256_loadu_ps(srcPtrTemp[0]); + pRow[1] = _mm256_loadu_ps(srcPtrTemp[1]); + pRow[2] = _mm256_loadu_ps(srcPtrTemp[2]); + pRow[3] = _mm256_loadu_ps(srcPtrTemp[3]); + pRow[4] = _mm256_loadu_ps(srcPtrTemp[4]); + for (int k = 5; k < rowKernelLoopLimit; k++) + pRow[k] = _mm256_loadu_ps(srcPtrTemp[k]); + for (int k = rowKernelLoopLimit; k < 9; k++) + pRow[k] = avx_p0; +} + +// -------------------- Filter load functions for F16 bitdepth -------------------- + +// load function for 3x3 kernel size +inline void rpp_load_dilate_float_3x3_host(__m256 *pRow, Rpp16f **srcPtrTemp, Rpp32s rowKernelLoopLimit) +{ + // irrespective of row location, we need to load 2 rows for 3x3 kernel + pRow[0] = _mm256_cvtph_ps(_mm_castps_si128(_mm_loadu_ps(reinterpret_cast(srcPtrTemp[0])))); + pRow[1] = _mm256_cvtph_ps(_mm_castps_si128(_mm_loadu_ps(reinterpret_cast(srcPtrTemp[1])))); + if (rowKernelLoopLimit == 3) + pRow[2] = _mm256_cvtph_ps(_mm_castps_si128(_mm_loadu_ps(reinterpret_cast(srcPtrTemp[2])))); + else + pRow[2] = avx_p0; +} + +// load function for 5x5 kernel size +inline void rpp_load_dilate_float_5x5_host(__m256 *pRow, Rpp16f **srcPtrTemp, Rpp32s rowKernelLoopLimit) +{ + // irrespective of row location, we need to load 3 rows for 5x5 kernel + pRow[0] = _mm256_cvtph_ps(_mm_castps_si128(_mm_loadu_ps(reinterpret_cast(srcPtrTemp[0])))); + pRow[1] = _mm256_cvtph_ps(_mm_castps_si128(_mm_loadu_ps(reinterpret_cast(srcPtrTemp[1])))); + pRow[2] = _mm256_cvtph_ps(_mm_castps_si128(_mm_loadu_ps(reinterpret_cast(srcPtrTemp[2])))); + for (int k = 3; k < rowKernelLoopLimit; k++) + pRow[k] = _mm256_cvtph_ps(_mm_castps_si128(_mm_loadu_ps(reinterpret_cast(srcPtrTemp[k])))); + for (int k = rowKernelLoopLimit; k < 5; k++) + pRow[k] = avx_p0; +} + +// load function for 7x7 kernel size +inline void rpp_load_dilate_float_7x7_host(__m256 *pRow, Rpp16f **srcPtrTemp, Rpp32s rowKernelLoopLimit) +{ + // irrespective of row location, we need to load 4 rows for 7x7 kernel + pRow[0] = _mm256_cvtph_ps(_mm_castps_si128(_mm_loadu_ps(reinterpret_cast(srcPtrTemp[0])))); + pRow[1] = _mm256_cvtph_ps(_mm_castps_si128(_mm_loadu_ps(reinterpret_cast(srcPtrTemp[1])))); + pRow[2] = _mm256_cvtph_ps(_mm_castps_si128(_mm_loadu_ps(reinterpret_cast(srcPtrTemp[2])))); + pRow[3] = _mm256_cvtph_ps(_mm_castps_si128(_mm_loadu_ps(reinterpret_cast(srcPtrTemp[3])))); + for (int k = 4; k < rowKernelLoopLimit; k++) + pRow[k] = _mm256_cvtph_ps(_mm_castps_si128(_mm_loadu_ps(reinterpret_cast(srcPtrTemp[k])))); + for (int k = rowKernelLoopLimit; k < 7; k++) + pRow[k] = avx_p0; +} + +// load function for 9x9 kernel size +inline void rpp_load_dilate_float_9x9_host(__m256 *pRow, Rpp16f **srcPtrTemp, Rpp32s rowKernelLoopLimit) +{ + // irrespective of row location, we need to load 5 rows for 9x9 kernel + pRow[0] = _mm256_cvtph_ps(_mm_castps_si128(_mm_loadu_ps(reinterpret_cast(srcPtrTemp[0])))); + pRow[1] = _mm256_cvtph_ps(_mm_castps_si128(_mm_loadu_ps(reinterpret_cast(srcPtrTemp[1])))); + pRow[2] = _mm256_cvtph_ps(_mm_castps_si128(_mm_loadu_ps(reinterpret_cast(srcPtrTemp[2])))); + pRow[3] = _mm256_cvtph_ps(_mm_castps_si128(_mm_loadu_ps(reinterpret_cast(srcPtrTemp[3])))); + pRow[4] = _mm256_cvtph_ps(_mm_castps_si128(_mm_loadu_ps(reinterpret_cast(srcPtrTemp[4])))); + for (int k = 5; k < rowKernelLoopLimit; k++) + pRow[k] = _mm256_cvtph_ps(_mm_castps_si128(_mm_loadu_ps(reinterpret_cast(srcPtrTemp[k])))); + for (int k = rowKernelLoopLimit; k < 9; k++) + pRow[k] = avx_p0; +} + +#endif //RPP_CPU_FILTER_HPP \ No newline at end of file diff --git a/src/include/cpu/rpp_cpu_simd.hpp b/src/include/cpu/rpp_cpu_simd.hpp index c24ef90da..8f4749508 100644 --- a/src/include/cpu/rpp_cpu_simd.hpp +++ b/src/include/cpu/rpp_cpu_simd.hpp @@ -229,6 +229,17 @@ inline void rpp_mm_print_epi32(__m128i vPrintArray) } } +inline void rpp_mm_print_epi16(__m128i vPrintArray) +{ + unsigned short int printArray[8]; + _mm_storeu_si128((__m128i *)printArray, vPrintArray); + printf("\n"); + for (int ct = 0; ct < 8; ct++) + { + printf("%hu ", printArray[ct]); + } +} + inline void rpp_mm_print_ps(__m128 vPrintArray) { float printArray[4]; @@ -1205,9 +1216,9 @@ inline void rpp_store48_f32pln3_to_f16pln3_avx(Rpp16f *dstPtrR, Rpp16f *dstPtrG, inline void rpp_store48_f32pln3_to_f32pkd3_avx(Rpp32f *dstPtr, __m256 *p) { __m128 p128[4]; - p128[0] = _mm256_extractf128_ps(p[0], 0); - p128[1] = _mm256_extractf128_ps(p[2], 0); - p128[2] = _mm256_extractf128_ps(p[4], 0); + p128[0] = _mm256_castps256_ps128(p[0]); + p128[1] = _mm256_castps256_ps128(p[2]); + p128[2] = _mm256_castps256_ps128(p[4]); _MM_TRANSPOSE4_PS(p128[0], p128[1], p128[2], p128[3]); _mm_storeu_ps(dstPtr, p128[0]); _mm_storeu_ps(dstPtr + 3, p128[1]); @@ -1222,9 +1233,9 @@ inline void rpp_store48_f32pln3_to_f32pkd3_avx(Rpp32f *dstPtr, __m256 *p) _mm_storeu_ps(dstPtr + 18, p128[2]); _mm_storeu_ps(dstPtr + 21, p128[3]); - p128[0] = _mm256_extractf128_ps(p[1], 0); - p128[1] = _mm256_extractf128_ps(p[3], 0); - p128[2] = _mm256_extractf128_ps(p[5], 0); + p128[0] = _mm256_castps256_ps128(p[1]); + p128[1] = _mm256_castps256_ps128(p[3]); + p128[2] = _mm256_castps256_ps128(p[5]); _MM_TRANSPOSE4_PS(p128[0], p128[1], p128[2], p128[3]); _mm_storeu_ps(dstPtr + 24, p128[0]); _mm_storeu_ps(dstPtr + 27, p128[1]); @@ -4136,4 +4147,149 @@ inline void rpp_store24_f32pkd3_to_f32pkd3_avx(Rpp32f* dstPtr, __m256 *p) _mm256_storeu_ps(dstPtr + 16, p[2]); /* Store RGB set 3 */ } +inline void rpp_convert24_pkd3_to_pln3(__m128i &pxLower, __m128i &pxUpper, __m128i *pxDstChn) +{ + // pxLower = R1 G1 B1 R2 G2 B2 R3 G3 B3 R4 G4 B4 R5 G5 B5 R6 + // pxUpper = G6 B6 R7 G7 B7 R8 G8 B8 0 0 0 0 0 0 0 0 + // shuffle1 - R1 R2 R3 R4 0 0 0 0 0 0 0 0 0 0 0 0 + // shuffle2 - G1 G2 G3 G4 0 0 0 0 0 0 0 0 0 0 0 0 + // shuffle3 - B1 B2 B3 B4 0 0 0 0 0 0 0 0 0 0 0 0 + // blend - G6 B6 R7 G7 B7 R8 G8 B8 0 0 0 0 R5 G5 B5 R6 + // R5 R6 R7 R8 G5 G6 G7 G8 B5 B6 B7 B8 0 0 0 0 + // R1 R2 R3 R4 R5 R6 R7 R8 0 0 0 0 0 0 0 0 + // G1 G2 G3 G4 G5 G6 G7 G8 0 0 0 0 0 0 0 0 + // B1 B2 B3 B4 0 0 0 0 B5 B6 B7 B8 0 0 0 0 + // B1 B2 B3 B4 B5 B6 B7 B8 0 0 0 0 0 0 0 0 + + __m128i pxTempUpper = _mm_blend_epi16(pxUpper, pxLower, 192); + __m128i xmm_shuffle_mask = _mm_setr_epi8(12, 15, 2, 5, 13, 0, 3, 6, 14, 1, 4, 7, 0x80, 0x80, 0x80, 0x80); + pxTempUpper = _mm_shuffle_epi8(pxTempUpper, xmm_shuffle_mask); + + pxDstChn[0] = _mm_unpacklo_epi32(_mm_shuffle_epi8(pxLower, xmm_char_maskR), pxTempUpper); + pxDstChn[1] = _mm_blend_epi16(_mm_shuffle_epi8(pxLower, xmm_char_maskG), pxTempUpper, 12); + + xmm_shuffle_mask = _mm_setr_epi8(0, 1, 2, 3, 8, 9, 10, 11, 0x80, 0x80, 0x80, 0x80,0x80, 0x80, 0x80, 0x80); + pxDstChn[2] = _mm_shuffle_epi8(_mm_blend_epi16(_mm_shuffle_epi8(pxLower, xmm_char_maskB), pxTempUpper, 48), xmm_shuffle_mask); +} + +inline void rpp_convert72_pln3_to_pkd3(__m256i *pxSrc, __m128i *pxDst) +{ + const __m128i pxMask = _mm_setr_epi8(0, 1, 12, 2, 3, 13, 4, 5, 14, 6, 7, 15, 0x80, 0x80, 0x80, 0x80); + + __m256i px[2]; + px[0] = _mm256_unpacklo_epi8(pxSrc[0], pxSrc[1]); + px[1] = _mm256_unpackhi_epi8(pxSrc[0], pxSrc[1]); + + __m128i pxTemp[4]; + // RGB 1-8 + pxTemp[0] = _mm256_castsi256_si128(px[0]); + pxTemp[1] = _mm256_castsi256_si128(pxSrc[2]); + + // RGB 1-4, shuffle to get correct order + // RGB 5-8, shuffle to get correct order + pxTemp[2] = _mm_unpacklo_epi64(pxTemp[0], pxTemp[1]); + pxTemp[3] = _mm_unpacklo_epi64(_mm_srli_si128(pxTemp[0], 8), pxTemp[1]); + pxDst[0] = _mm_shuffle_epi8(pxTemp[2], xmm_store4_pkd_pixels); + pxDst[1] = _mm_shuffle_epi8(pxTemp[3], pxMask); + + // RGB 9-16 + pxTemp[0] = _mm256_castsi256_si128(px[1]), + pxTemp[1] = _mm256_castsi256_si128(pxSrc[2]); + + // RGB 9-12, shuffle to get correct order + // RGB 13-15, shuffle to get correct order + pxTemp[2] = _mm_unpacklo_epi64(pxTemp[0], _mm_srli_si128(pxTemp[1], 8)); + pxTemp[3] = _mm_unpackhi_epi64(pxTemp[0], pxTemp[1]); + pxDst[2] = _mm_shuffle_epi8(pxTemp[2], xmm_store4_pkd_pixels); + pxDst[3] = _mm_shuffle_epi8(pxTemp[3], pxMask); + + // RGB 17-24 + pxTemp[0] = _mm256_extracti128_si256(px[0], 1), + pxTemp[1] = _mm256_extracti128_si256(pxSrc[2], 1); + + // RGB 17-20, shuffle to get correct order + // RGB 21-24, shuffle to get correct order + pxTemp[2] = _mm_unpacklo_epi64(pxTemp[0], pxTemp[1]); + pxTemp[3] = _mm_unpacklo_epi64(_mm_srli_si128(pxTemp[0], 8), pxTemp[1]); + pxDst[4] = _mm_shuffle_epi8(pxTemp[2], xmm_store4_pkd_pixels); + pxDst[5] = _mm_shuffle_epi8(pxTemp[3], pxMask); +} + +inline void rpp_convert48_pln3_to_pkd3(__m128i *pxSrc, __m128i *pxDst) +{ + const __m128i pxMask = _mm_setr_epi8(0, 1, 12, 2, 3, 13, 4, 5, 14, 6, 7, 15, 0x80, 0x80, 0x80, 0x80); + + __m128i pxTemp[3]; + pxTemp[0] = _mm_unpacklo_epi8(pxSrc[0], pxSrc[1]); + + // RGB 1-4, shuffle to get correct order + // RGB 5-8, shuffle to get correct order + pxTemp[1] = _mm_unpacklo_epi64(pxTemp[0], pxSrc[2]); + pxTemp[2] = _mm_unpacklo_epi64(_mm_srli_si128(pxTemp[0], 8), pxSrc[2]); + pxDst[0] = _mm_shuffle_epi8(pxTemp[1], xmm_store4_pkd_pixels); + pxDst[1] = _mm_shuffle_epi8(pxTemp[2], pxMask); + + pxTemp[0] = _mm_unpackhi_epi8(pxSrc[0], pxSrc[1]); + + // RGB 9-12, shuffle to get correct order + // RGB 13-16, shuffle to get correct order + pxTemp[1] = _mm_unpackhi_epi64(_mm_slli_si128(pxTemp[0], 8), pxSrc[2]); + pxTemp[2] = _mm_unpackhi_epi64(pxTemp[0], pxSrc[2]); + pxDst[2] = _mm_shuffle_epi8(pxTemp[1], xmm_store4_pkd_pixels); + pxDst[3] = _mm_shuffle_epi8(pxTemp[2], pxMask); +} + +inline void rpp_convert12_f32pkd3_to_f32pln3(__m256 *pSrc, __m128 *pDst) +{ + __m128 pSrcPkd[3], pTemp; + pSrcPkd[0] = _mm256_castps256_ps128(pSrc[0]); + pSrcPkd[1] = _mm256_extractf128_ps(pSrc[0], 1); + pSrcPkd[2] = _mm256_castps256_ps128(pSrc[1]); + + pTemp = _mm_blend_ps(pSrcPkd[0], pSrcPkd[1], 4); + pTemp = _mm_blend_ps(pTemp, pSrcPkd[2], 2); + pDst[0] = _mm_shuffle_ps(pTemp, pTemp, 108); + + pTemp = _mm_blend_ps(pSrcPkd[0], pSrcPkd[1], 9); + pTemp = _mm_blend_ps(pTemp, pSrcPkd[2], 4); + pDst[1] = _mm_shuffle_ps(pTemp, pTemp, 177); + + pTemp = _mm_blend_ps(pSrcPkd[0], pSrcPkd[1], 2); + pTemp = _mm_blend_ps(pTemp, pSrcPkd[2], 9); + pDst[2] = _mm_shuffle_ps(pTemp, pTemp, 198); +} + +inline void rpp_store16_float(Rpp32f *dstPtrTemp, __m256 *pDst) +{ + _mm256_storeu_ps(dstPtrTemp, pDst[0]); + _mm256_storeu_ps(dstPtrTemp + 8, pDst[1]); +} + +inline void rpp_store16_float(Rpp16f *dstPtrTemp, __m256 *pDst) +{ + __m128i pxDst[2]; + pxDst[0] = _mm256_cvtps_ph(pDst[0], _MM_FROUND_TO_ZERO | _MM_FROUND_NO_EXC); + pxDst[1] = _mm256_cvtps_ph(pDst[1], _MM_FROUND_TO_ZERO | _MM_FROUND_NO_EXC); + _mm_storeu_si128((__m128i *)dstPtrTemp, pxDst[0]); + _mm_storeu_si128((__m128i *)(dstPtrTemp + 8), pxDst[1]); +} + +inline void rpp_store12_float_pkd_pln(Rpp32f **dstPtrTempChannels, __m128 *pDst) +{ + _mm_storeu_ps(dstPtrTempChannels[0], pDst[0]); + _mm_storeu_ps(dstPtrTempChannels[1], pDst[1]); + _mm_storeu_ps(dstPtrTempChannels[2], pDst[2]); +} + +inline void rpp_store12_float_pkd_pln(Rpp16f **dstPtrTempChannels, __m128 *pDst) +{ + __m128i pxDst[3]; + pxDst[0] = _mm_cvtps_ph(pDst[0], _MM_FROUND_TO_ZERO | _MM_FROUND_NO_EXC); + pxDst[1] = _mm_cvtps_ph(pDst[1], _MM_FROUND_TO_ZERO | _MM_FROUND_NO_EXC); + pxDst[2] = _mm_cvtps_ph(pDst[2], _MM_FROUND_TO_ZERO | _MM_FROUND_NO_EXC); + _mm_storeu_si128((__m128i *)(dstPtrTempChannels[0]), pxDst[0]); + _mm_storeu_si128((__m128i *)(dstPtrTempChannels[1]), pxDst[1]); + _mm_storeu_si128((__m128i *)(dstPtrTempChannels[2]), pxDst[2]); +} + #endif //AMD_RPP_RPP_CPU_SIMD_HPP \ No newline at end of file diff --git a/src/modules/cpu/host_tensor_morphological_operations.hpp b/src/modules/cpu/host_tensor_morphological_operations.hpp new file mode 100644 index 000000000..93ac8fc10 --- /dev/null +++ b/src/modules/cpu/host_tensor_morphological_operations.hpp @@ -0,0 +1,30 @@ +/* +MIT License + +Copyright (c) 2019 - 2024 Advanced Micro Devices, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#ifndef HOST_TENSOR_MORPHOLOGICAL_OPERATIONS_HPP +#define HOST_TENSOR_MORPHOLOGICAL_OPERATIONS_HPP + +#include "kernel/dilate.hpp" + +#endif // HOST_TENSOR_MORPHOLOGICAL_OPERATIONS_HPP diff --git a/src/modules/cpu/kernel/dilate.hpp b/src/modules/cpu/kernel/dilate.hpp new file mode 100644 index 000000000..877043ba4 --- /dev/null +++ b/src/modules/cpu/kernel/dilate.hpp @@ -0,0 +1,2775 @@ +/* +MIT License + +Copyright (c) 2019 - 2024 Advanced Micro Devices, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#include "rppdefs.h" +#include "rpp_cpu_common.hpp" +#include "rpp_cpu_filter.hpp" + +// generic raw c code for dilate +template +inline void dilate_generic_tensor(T **srcPtrTemp, T *dstPtrTemp, Rpp32s columnIndex, + Rpp32u kernelSize, Rpp32u padLength, Rpp32u unpaddedWidth, Rpp32s rowKernelLoopLimit, + Rpp32f kernelSizeInverseSquare, Rpp32u channels = 1) +{ + T result; + if constexpr (std::is_same::value) + result = static_cast(0); + else if constexpr (std::is_same::value) + result = static_cast(-128); + else if constexpr (std::is_same::value || std::is_same::value) + result = static_cast(0.0); + Rpp32s columnKernelLoopLimit = kernelSize; + + // find the colKernelLoopLimit based on columnIndex + get_kernel_loop_limit(columnIndex, columnKernelLoopLimit, padLength, unpaddedWidth); + for (int i = 0; i < rowKernelLoopLimit; i++) + for (int j = 0, k = 0 ; j < columnKernelLoopLimit; j++, k += channels) + result = std::max(result, srcPtrTemp[i][k]); + *dstPtrTemp = result; +} + +// process padLength number of columns in each row for PLN-PLN case +// left border pixels in image which does not have required pixels in 3x3/5x5/7x7/9x9 box, process them separately +template +inline void process_left_border_columns_pln_pln(T **srcPtrTemp, T *dstPtrTemp, Rpp32u kernelSize, Rpp32u padLength, + Rpp32u unpaddedWidth, Rpp32s rowKernelLoopLimit, Rpp32f kernelSizeInverseSquare) +{ + for (int k = 0; k < padLength; k++) + { + dilate_generic_tensor(srcPtrTemp, dstPtrTemp, k, kernelSize, padLength, unpaddedWidth, rowKernelLoopLimit, kernelSizeInverseSquare); + dstPtrTemp++; + } +} + +// process padLength * 3 number of columns in each row for PKD-PKD case +// left border pixels in image which does not have required pixels in 3x3/5x5/7x7/9x9 box, process them separately +template +inline void process_left_border_columns_pkd_pkd(T **srcPtrTemp, T **srcPtrRow, T *dstPtrTemp, Rpp32u kernelSize, Rpp32u padLength, + Rpp32u unpaddedWidth, Rpp32s rowKernelLoopLimit, Rpp32f kernelSizeInverseSquare) +{ + for (int c = 0; c < 3; c++) + { + T *dstPtrTempChannel = dstPtrTemp + c; + for (int k = 0; k < padLength; k++) + { + dilate_generic_tensor(srcPtrTemp, dstPtrTempChannel, k, kernelSize, padLength, unpaddedWidth, rowKernelLoopLimit, kernelSizeInverseSquare, 3); + dstPtrTempChannel += 3; + } + increment_row_ptrs(srcPtrTemp, kernelSize, 1); + } + // reset source to initial position + for (int k = 0; k < kernelSize; k++) + srcPtrTemp[k] = srcPtrRow[k]; +} + +// process padLength * 3 number of columns in each row for PKD-PLN case +// left border pixels in image which does not have required pixels in 3x3/5x5/7x7/9x9 box, process them separately +template +inline void process_left_border_columns_pkd_pln(T **srcPtrTemp, T **srcPtrRow, T **dstPtrTempChannels, Rpp32u kernelSize, Rpp32u padLength, + Rpp32u unpaddedWidth, Rpp32s rowKernelLoopLimit, Rpp32f kernelSizeInverseSquare) +{ + for (int c = 0; c < 3; c++) + { + for (int k = 0; k < padLength; k++) + { + dilate_generic_tensor(srcPtrTemp, dstPtrTempChannels[c], k, kernelSize, padLength, unpaddedWidth, rowKernelLoopLimit, kernelSizeInverseSquare, 3); + dstPtrTempChannels[c] += 1; + } + increment_row_ptrs(srcPtrTemp, kernelSize, 1); + } + + // reset source to initial position + for (int k = 0; k < kernelSize; k++) + srcPtrTemp[k] = srcPtrRow[k]; +} + +// -------------------- Set 0 dilate compute functions -------------------- + +// unpack lower half of 3 256 bit registers and add (used for 3x3 kernel size U8/I8 variants) +inline void unpacklo_and_max_3x3_host(__m256i *pxRow, __m256i *pxDst) +{ + pxDst[0] = _mm256_unpacklo_epi8(pxRow[0], avx_px0); + pxDst[0] = _mm256_max_epi16(pxDst[0], _mm256_unpacklo_epi8(pxRow[1], avx_px0)); + pxDst[0] = _mm256_max_epi16(pxDst[0], _mm256_unpacklo_epi8(pxRow[2], avx_px0)); +} + +// unpack higher half of 3 256 bit registers and add (used for 3x3 kernel size U8/I8 variants) +inline void unpackhi_and_max_3x3_host(__m256i *pxRow, __m256i *pxDst) +{ + pxDst[0] = _mm256_unpackhi_epi8(pxRow[0], avx_px0); + pxDst[0] = _mm256_max_epi16(pxDst[0], _mm256_unpackhi_epi8(pxRow[1], avx_px0)); + pxDst[0] = _mm256_max_epi16(pxDst[0], _mm256_unpackhi_epi8(pxRow[2], avx_px0)); +} + +// unpack lower half of 5 256 bit registers and add (used for 5x5 kernel size U8/I8 variants) +inline void unpacklo_and_max_5x5_host(__m256i *pxRow, __m256i *pxDst) +{ + pxDst[0] = _mm256_unpacklo_epi8(pxRow[0], avx_px0); + pxDst[0] = _mm256_max_epi16(pxDst[0], _mm256_unpacklo_epi8(pxRow[1], avx_px0)); + pxDst[0] = _mm256_max_epi16(pxDst[0], _mm256_unpacklo_epi8(pxRow[2], avx_px0)); + pxDst[0] = _mm256_max_epi16(pxDst[0], _mm256_unpacklo_epi8(pxRow[3], avx_px0)); + pxDst[0] = _mm256_max_epi16(pxDst[0], _mm256_unpacklo_epi8(pxRow[4], avx_px0)); +} + +// unpack higher half of 5 256 bit registers and add (used for 5x5 kernel size U8/I8 variants) +inline void unpackhi_and_max_5x5_host(__m256i *pxRow, __m256i *pxDst) +{ + pxDst[0] = _mm256_unpackhi_epi8(pxRow[0], avx_px0); + pxDst[0] = _mm256_max_epi16(pxDst[0], _mm256_unpackhi_epi8(pxRow[1], avx_px0)); + pxDst[0] = _mm256_max_epi16(pxDst[0], _mm256_unpackhi_epi8(pxRow[2], avx_px0)); + pxDst[0] = _mm256_max_epi16(pxDst[0], _mm256_unpackhi_epi8(pxRow[3], avx_px0)); + pxDst[0] = _mm256_max_epi16(pxDst[0], _mm256_unpackhi_epi8(pxRow[4], avx_px0)); +} + +// unpack lower half of 7 256 bit registers and add (used for 7x7 kernel size U8/I8 variants) +inline void unpacklo_and_max_7x7_host(__m256i *pxRow, __m256i *pxDst) +{ + pxDst[0] = _mm256_unpacklo_epi8(pxRow[0], avx_px0); + pxDst[0] = _mm256_max_epi16(pxDst[0], _mm256_unpacklo_epi8(pxRow[1], avx_px0)); + pxDst[0] = _mm256_max_epi16(pxDst[0], _mm256_unpacklo_epi8(pxRow[2], avx_px0)); + pxDst[0] = _mm256_max_epi16(pxDst[0], _mm256_unpacklo_epi8(pxRow[3], avx_px0)); + pxDst[0] = _mm256_max_epi16(pxDst[0], _mm256_unpacklo_epi8(pxRow[4], avx_px0)); + pxDst[0] = _mm256_max_epi16(pxDst[0], _mm256_unpacklo_epi8(pxRow[5], avx_px0)); + pxDst[0] = _mm256_max_epi16(pxDst[0], _mm256_unpacklo_epi8(pxRow[6], avx_px0)); +} + +// unpack higher half of 7 256 bit registers and add (used for 7x7 kernel size U8/I8 variants) +inline void unpackhi_and_max_7x7_host(__m256i *pxRow, __m256i *pxDst) +{ + pxDst[0] = _mm256_unpackhi_epi8(pxRow[0], avx_px0); + pxDst[0] = _mm256_max_epi16(pxDst[0], _mm256_unpackhi_epi8(pxRow[1], avx_px0)); + pxDst[0] = _mm256_max_epi16(pxDst[0], _mm256_unpackhi_epi8(pxRow[2], avx_px0)); + pxDst[0] = _mm256_max_epi16(pxDst[0], _mm256_unpackhi_epi8(pxRow[3], avx_px0)); + pxDst[0] = _mm256_max_epi16(pxDst[0], _mm256_unpackhi_epi8(pxRow[4], avx_px0)); + pxDst[0] = _mm256_max_epi16(pxDst[0], _mm256_unpackhi_epi8(pxRow[5], avx_px0)); + pxDst[0] = _mm256_max_epi16(pxDst[0], _mm256_unpackhi_epi8(pxRow[6], avx_px0)); +} + +// unpack lower half of 9 256 bit registers and add (used for 9x9 kernel size U8/I8 variants) +inline void unpacklo_and_max_9x9_host(__m256i *pxRow, __m256i *pxDst) +{ + pxDst[0] = _mm256_unpacklo_epi8(pxRow[0], avx_px0); + pxDst[0] = _mm256_max_epi16(pxDst[0], _mm256_unpacklo_epi8(pxRow[1], avx_px0)); + pxDst[0] = _mm256_max_epi16(pxDst[0], _mm256_unpacklo_epi8(pxRow[2], avx_px0)); + pxDst[0] = _mm256_max_epi16(pxDst[0], _mm256_unpacklo_epi8(pxRow[3], avx_px0)); + pxDst[0] = _mm256_max_epi16(pxDst[0], _mm256_unpacklo_epi8(pxRow[4], avx_px0)); + pxDst[0] = _mm256_max_epi16(pxDst[0], _mm256_unpacklo_epi8(pxRow[5], avx_px0)); + pxDst[0] = _mm256_max_epi16(pxDst[0], _mm256_unpacklo_epi8(pxRow[6], avx_px0)); + pxDst[0] = _mm256_max_epi16(pxDst[0], _mm256_unpacklo_epi8(pxRow[7], avx_px0)); + pxDst[0] = _mm256_max_epi16(pxDst[0], _mm256_unpacklo_epi8(pxRow[8], avx_px0)); +} + +// unpack higher half of 9 256 bit registers and add (used for 9x9 kernel size U8/I8 variants) +inline void unpackhi_and_max_9x9_host(__m256i *pxRow, __m256i *pxDst) +{ + pxDst[0] = _mm256_unpackhi_epi8(pxRow[0], avx_px0); + pxDst[0] = _mm256_max_epi16(pxDst[0], _mm256_unpackhi_epi8(pxRow[1], avx_px0)); + pxDst[0] = _mm256_max_epi16(pxDst[0], _mm256_unpackhi_epi8(pxRow[2], avx_px0)); + pxDst[0] = _mm256_max_epi16(pxDst[0], _mm256_unpackhi_epi8(pxRow[3], avx_px0)); + pxDst[0] = _mm256_max_epi16(pxDst[0], _mm256_unpackhi_epi8(pxRow[4], avx_px0)); + pxDst[0] = _mm256_max_epi16(pxDst[0], _mm256_unpackhi_epi8(pxRow[5], avx_px0)); + pxDst[0] = _mm256_max_epi16(pxDst[0], _mm256_unpackhi_epi8(pxRow[6], avx_px0)); + pxDst[0] = _mm256_max_epi16(pxDst[0], _mm256_unpackhi_epi8(pxRow[7], avx_px0)); + pxDst[0] = _mm256_max_epi16(pxDst[0], _mm256_unpackhi_epi8(pxRow[8], avx_px0)); +} + +// add 3 256 bit registers (used for 3x3 kernel size F32/F16 variants) +inline void max_rows_3x3(__m256 *pRow, __m256 *pDst) +{ + pDst[0] = _mm256_max_ps(pRow[0], pRow[1]); + pDst[0] = _mm256_max_ps(pDst[0], pRow[2]); +} + +// add 5 256 bit registers (used for 5x5 kernel size F32/F16 variants) +inline void max_rows_5x5(__m256 *pRow, __m256 *pDst) +{ + pDst[0] = _mm256_max_ps(_mm256_max_ps(pRow[0], pRow[1]), pRow[2]); + pDst[0] = _mm256_max_ps(pDst[0], _mm256_max_ps(pRow[3], pRow[4])); +} + +// add 7 256 bit registers (used for 7x7 kernel size F32/F16 variants) +inline void max_rows_7x7(__m256 *pRow, __m256 *pDst) +{ + pDst[0] = _mm256_max_ps(_mm256_max_ps(pRow[0], pRow[1]), pRow[2]); + pDst[0] = _mm256_max_ps(pDst[0], _mm256_max_ps(_mm256_max_ps(pRow[3], pRow[4]), pRow[5])); + pDst[0] = _mm256_max_ps(pDst[0], pRow[6]); +} + +// add 9 256 bit registers (used for 9x9 kernel size F32/F16 variants) +inline void max_rows_9x9(__m256 *pRow, __m256 *pDst) +{ + pDst[0] = _mm256_max_ps(_mm256_max_ps(pRow[0], pRow[1]), pRow[2]); + pDst[0] = _mm256_max_ps(pDst[0], _mm256_max_ps(_mm256_max_ps(pRow[3], pRow[4]), pRow[5])); + pDst[0] = _mm256_max_ps(pDst[0], _mm256_max_ps(_mm256_max_ps(pRow[6], pRow[7]), pRow[8])); +} + +// #undef __AVX2__ +// #define __AVX2__ 0 + +template +RppStatus dilate_char_host_tensor(T *srcPtr, + RpptDescPtr srcDescPtr, + T *dstPtr, + RpptDescPtr dstDescPtr, + Rpp32u kernelSize, + RpptROIPtr roiTensorPtrSrc, + RpptRoiType roiType, + RppLayoutParams layoutParams, + rpp::Handle& handle) +{ + RpptROI roiDefault = {0, 0, (Rpp32s)srcDescPtr->w, (Rpp32s)srcDescPtr->h}; + Rpp32u numThreads = handle.GetNumThreads(); + static_assert((std::is_same::value || std::is_same::value), "T must be Rpp8u or Rpp8s"); + + if ((kernelSize != 3) && (kernelSize != 5) && (kernelSize != 7) && (kernelSize != 9)) + return dilate_generic_host_tensor(srcPtr, srcDescPtr, dstPtr, dstDescPtr, kernelSize, roiTensorPtrSrc, roiType, layoutParams, handle); + + // set the required masks array needed for shuffle operations +#if __AVX2__ + __m128i pxMaskPln[7] = {xmm_pxMaskRotate0To1, xmm_pxMaskRotate0To3, xmm_pxMaskRotate0To5, xmm_pxMaskRotate0To7, xmm_pxMaskRotate0To9, xmm_pxMaskRotate0To11, xmm_pxMaskRotate0To13}; + __m128i pxMaskPkd[7] = {xmm_pxMaskRotate0To5, xmm_pxMaskRotate0To11, xmm_pxMaskRotate0To1, xmm_pxMaskRotate0To7, xmm_pxMaskRotate0To13, xmm_pxMaskRotate0To3, xmm_pxMaskRotate0To9}; +#endif + + omp_set_dynamic(0); +#pragma omp parallel for num_threads(numThreads) + for(int batchCount = 0; batchCount < dstDescPtr->n; batchCount++) + { + RpptROI roi; + RpptROIPtr roiPtrInput = &roiTensorPtrSrc[batchCount]; + compute_roi_validation_host(roiPtrInput, &roi, &roiDefault, roiType); + + T *srcPtrImage, *dstPtrImage; + srcPtrImage = srcPtr + batchCount * srcDescPtr->strides.nStride; + dstPtrImage = dstPtr + batchCount * dstDescPtr->strides.nStride; + + Rpp32u padLength = kernelSize / 2; + Rpp32u bufferLength = roi.xywhROI.roiWidth * layoutParams.bufferMultiplier; + Rpp32u unpaddedHeight = roi.xywhROI.roiHeight - padLength; + Rpp32u unpaddedWidth = roi.xywhROI.roiWidth - padLength; + + Rpp32f kernelSizeInverseSquare = 1.0 / (kernelSize * kernelSize); +#if __AVX2__ + // set the register order needed for blend operations + Rpp32u blendRegisterOrder[7] = {0, 0, 1, 1, 1, 2, 2}; + if (srcDescPtr->layout == RpptLayout::NCHW) + std::fill_n(blendRegisterOrder, 7, 0); +#endif + T *srcPtrChannel, *dstPtrChannel; + srcPtrChannel = srcPtrImage + (roi.xywhROI.xy.y * srcDescPtr->strides.hStride) + (roi.xywhROI.xy.x * layoutParams.bufferMultiplier); + dstPtrChannel = dstPtrImage; + if (kernelSize == 3) + { + T *srcPtrRow[3], *dstPtrRow; + for (int i = 0; i < 3; i++) + srcPtrRow[i] = srcPtrChannel + i * srcDescPtr->strides.hStride; + dstPtrRow = dstPtrChannel; + + // dilate without fused output-layout toggle (NCHW -> NCHW) + if ((srcDescPtr->layout == RpptLayout::NCHW) && (dstDescPtr->layout == RpptLayout::NCHW)) + { + /* exclude 2 * padLength number of columns from alignedLength calculation + since padLength number of columns from the beginning and end of each row will be computed using raw c code */ + Rpp32u alignedLength = ((bufferLength - (2 * padLength)) / 24) * 24; + for (int c = 0; c < srcDescPtr->c; c++) + { + srcPtrRow[0] = srcPtrChannel; + srcPtrRow[1] = srcPtrRow[0] + srcDescPtr->strides.hStride; + srcPtrRow[2] = srcPtrRow[1] + srcDescPtr->strides.hStride; + dstPtrRow = dstPtrChannel; + for(int i = 0; i < roi.xywhROI.roiHeight; i++) + { + int vectorLoopCount = 0; + bool padLengthRows = (i < padLength) ? 1: 0; + T *srcPtrTemp[3] = {srcPtrRow[0], srcPtrRow[1], srcPtrRow[2]}; + T *dstPtrTemp = dstPtrRow; + + // get the number of rows needs to be loaded for the corresponding row + Rpp32s rowKernelLoopLimit = kernelSize; + get_kernel_loop_limit(i, rowKernelLoopLimit, padLength, unpaddedHeight); + process_left_border_columns_pln_pln(srcPtrTemp, dstPtrTemp, kernelSize, padLength, unpaddedWidth, rowKernelLoopLimit, kernelSizeInverseSquare); + dstPtrTemp += padLength; +#if __AVX2__ + // process alignedLength number of columns in each row + for (; vectorLoopCount < alignedLength; vectorLoopCount += 24) + { + __m256i pxRow[3], pxRowHalf[2], pxResult; + rpp_load_dilate_char_3x3_host(pxRow, srcPtrTemp, rowKernelLoopLimit); + + // unpack lower half and higher half of each of 3 loaded row values from 8 bit to 16 bit and add + unpacklo_and_max_3x3_host(pxRow, &pxRowHalf[0]); + unpackhi_and_max_3x3_host(pxRow, &pxRowHalf[1]); + + // perform blend and shuffle operations to get required order and add them + __m128i pxTemp[4]; + extract_4sse_registers(pxRowHalf, pxTemp); + blend_shuffle_max_3x3_host<1, 3>(&pxTemp[0], pxMaskPln, blendRegisterOrder); + blend_shuffle_max_3x3_host<1, 3>(&pxTemp[1], pxMaskPln, blendRegisterOrder); + blend_shuffle_max_3x3_host<1, 3>(&pxTemp[2], pxMaskPln, blendRegisterOrder); + + __m128i pxDst[2]; + pxDst[0] = _mm_packus_epi16(pxTemp[0], pxTemp[1]); + pxDst[1] = _mm_packus_epi16(pxTemp[2], xmm_px0); + + pxResult = _mm256_setr_m128i(pxDst[0], pxDst[1]); + if constexpr (std::is_same::value) + pxResult = _mm256_sub_epi8(pxResult, avx_pxConvertI8); + + _mm256_storeu_si256((__m256i *)dstPtrTemp, pxResult); + increment_row_ptrs(srcPtrTemp, kernelSize, 24); + dstPtrTemp += 24; + } +#endif + vectorLoopCount += padLength; + for (; vectorLoopCount < bufferLength; vectorLoopCount++) + { + dilate_generic_tensor(srcPtrTemp, dstPtrTemp, vectorLoopCount, kernelSize, padLength, unpaddedWidth, rowKernelLoopLimit, kernelSizeInverseSquare); + increment_row_ptrs(srcPtrTemp, kernelSize, 1); + dstPtrTemp++; + } + // for the first padLength rows, we need not increment the src row pointers to next rows + increment_row_ptrs(srcPtrRow, kernelSize, (!padLengthRows) ? srcDescPtr->strides.hStride : 0); + dstPtrRow += dstDescPtr->strides.hStride; + } + srcPtrChannel += srcDescPtr->strides.cStride; + dstPtrChannel += dstDescPtr->strides.cStride; + } + } + else if ((srcDescPtr->layout == RpptLayout::NHWC) && (dstDescPtr->layout == RpptLayout::NHWC)) + { + /* exclude ((2 * padLength) * 3) number of columns from alignedLength calculation + since (padLength * 3) number of columns from the beginning and end of each row will be computed using raw c code */ + Rpp32u alignedLength = ((bufferLength - (2 * padLength) * 3) / 24) * 24; + for(int i = 0; i < roi.xywhROI.roiHeight; i++) + { + int vectorLoopCount = 0; + bool padLengthRows = (i < padLength) ? 1: 0; + T *srcPtrTemp[3] = {srcPtrRow[0], srcPtrRow[1], srcPtrRow[2]}; + T *dstPtrTemp = dstPtrRow; + + Rpp32s rowKernelLoopLimit = kernelSize; + get_kernel_loop_limit(i, rowKernelLoopLimit, padLength, unpaddedHeight); + process_left_border_columns_pkd_pkd(srcPtrTemp, srcPtrRow, dstPtrTemp, kernelSize, padLength, unpaddedWidth, rowKernelLoopLimit, kernelSizeInverseSquare); + dstPtrTemp += padLength * 3; +#if __AVX2__ + // process remaining columns in each row + for (; vectorLoopCount < alignedLength; vectorLoopCount += 24) + { + __m256i pxRow[3], pxRowHalf[2], pxResult; + rpp_load_dilate_char_3x3_host(pxRow, srcPtrTemp, rowKernelLoopLimit); + + // unpack lower half and higher half of each of 3 loaded row values from 8 bit to 16 bit and add + unpacklo_and_max_3x3_host(pxRow, &pxRowHalf[0]); + unpackhi_and_max_3x3_host(pxRow, &pxRowHalf[1]); + + // perform blend and shuffle operations for the first 8 output values to get required order and add them + __m128i pxTemp[4]; + extract_4sse_registers(pxRowHalf, pxTemp); + blend_shuffle_max_3x3_host<7, 63>(&pxTemp[0], pxMaskPkd, blendRegisterOrder); + blend_shuffle_max_3x3_host<7, 63>(&pxTemp[1], pxMaskPkd, blendRegisterOrder); + blend_shuffle_max_3x3_host<7, 63>(&pxTemp[2], pxMaskPkd, blendRegisterOrder); + + __m128i pxDst[2]; + pxDst[0] = _mm_packus_epi16(pxTemp[0], pxTemp[1]); + pxDst[1] = _mm_packus_epi16(pxTemp[2], xmm_px0); + + pxResult = _mm256_setr_m128i(pxDst[0], pxDst[1]); + if constexpr (std::is_same::value) + pxResult = _mm256_sub_epi8(pxResult, avx_pxConvertI8); + + _mm256_storeu_si256((__m256i *)dstPtrTemp, pxResult); + increment_row_ptrs(srcPtrTemp, kernelSize, 24); + dstPtrTemp += 24; + } +#endif + vectorLoopCount += padLength * 3; + for (; vectorLoopCount < bufferLength; vectorLoopCount++) + { + dilate_generic_tensor(srcPtrTemp, dstPtrTemp, vectorLoopCount / 3, kernelSize, padLength, unpaddedWidth, rowKernelLoopLimit, kernelSizeInverseSquare, 3); + increment_row_ptrs(srcPtrTemp, kernelSize, 1); + dstPtrTemp++; + } + // for the first padLength rows, we need not increment the src row pointers to next rows + increment_row_ptrs(srcPtrRow, kernelSize, (!padLengthRows) ? srcDescPtr->strides.hStride : 0); + dstPtrRow += dstDescPtr->strides.hStride; + } + } + else if ((srcDescPtr->c == 3) && (srcDescPtr->layout == RpptLayout::NHWC) && (dstDescPtr->layout == RpptLayout::NCHW)) + { + /* exclude ((2 * padLength) * 3) number of columns from alignedLength calculation + since (padLength * 3) number of columns from the beginning and end of each row will be computed using raw c code */ + Rpp32u alignedLength = ((bufferLength - (2 * padLength) * 3) / 24) * 24; + T *dstPtrChannels[3]; + for (int i = 0; i < 3; i++) + dstPtrChannels[i] = dstPtrChannel + i * dstDescPtr->strides.cStride; + + for(int i = 0; i < roi.xywhROI.roiHeight; i++) + { + int vectorLoopCount = 0; + bool padLengthRows = (i < padLength) ? 1: 0; + T *srcPtrTemp[3] = {srcPtrRow[0], srcPtrRow[1], srcPtrRow[2]}; + T *dstPtrTempChannels[3] = {dstPtrChannels[0], dstPtrChannels[1], dstPtrChannels[2]}; + + Rpp32s rowKernelLoopLimit = kernelSize; + get_kernel_loop_limit(i, rowKernelLoopLimit, padLength, unpaddedHeight); + process_left_border_columns_pkd_pln(srcPtrTemp, srcPtrRow, dstPtrTempChannels, kernelSize, padLength, unpaddedWidth, rowKernelLoopLimit, kernelSizeInverseSquare); +#if __AVX2__ + // process remaining columns in each row + for (; vectorLoopCount < alignedLength; vectorLoopCount += 24) + { + __m256i pxRow[3], pxRowHalf[2]; + rpp_load_dilate_char_3x3_host(pxRow, srcPtrTemp, rowKernelLoopLimit); + + // unpack lower half and higher half of each of 3 loaded row values from 8 bit to 16 bit and add + unpacklo_and_max_3x3_host(pxRow, &pxRowHalf[0]); + unpackhi_and_max_3x3_host(pxRow, &pxRowHalf[1]); + + // perform blend and shuffle operations for the first 8 output values to get required order and add them + __m128i pxTemp[4]; + extract_4sse_registers(pxRowHalf, pxTemp); + blend_shuffle_max_3x3_host<7, 63>(&pxTemp[0], pxMaskPkd, blendRegisterOrder); + blend_shuffle_max_3x3_host<7, 63>(&pxTemp[1], pxMaskPkd, blendRegisterOrder); + blend_shuffle_max_3x3_host<7, 63>(&pxTemp[2], pxMaskPkd, blendRegisterOrder); + + __m128i pxDst[2]; + pxDst[0] = _mm_packus_epi16(pxTemp[0], pxTemp[1]); + pxDst[1] = _mm_packus_epi16(pxTemp[2], xmm_px0); + if constexpr (std::is_same::value) + { + pxDst[0] = _mm_sub_epi8(pxDst[0], xmm_pxConvertI8); + pxDst[1] = _mm_sub_epi8(pxDst[1], xmm_pxConvertI8); + } + + // convert from PKD3 to PLN3 and store channelwise + __m128i pxDstChn[3]; + rpp_convert24_pkd3_to_pln3(pxDst[0], pxDst[1], pxDstChn); + rpp_storeu_si64((__m128i *)(dstPtrTempChannels[0]), pxDstChn[0]); + rpp_storeu_si64((__m128i *)(dstPtrTempChannels[1]), pxDstChn[1]); + rpp_storeu_si64((__m128i *)(dstPtrTempChannels[2]), pxDstChn[2]); + increment_row_ptrs(srcPtrTemp, kernelSize, 24); + increment_row_ptrs(dstPtrTempChannels, kernelSize, 8); + } +#endif + vectorLoopCount += padLength * 3; + for (int c = 0; vectorLoopCount < bufferLength; vectorLoopCount++, c++) + { + int channel = c % 3; + dilate_generic_tensor(srcPtrTemp, dstPtrTempChannels[channel], vectorLoopCount / 3, kernelSize, padLength, unpaddedWidth, rowKernelLoopLimit, kernelSizeInverseSquare, 3); + increment_row_ptrs(srcPtrTemp, kernelSize, 1); + dstPtrTempChannels[channel]++; + } + // for the first padLength rows, we need not increment the src row pointers to next rows + increment_row_ptrs(srcPtrRow, kernelSize, (!padLengthRows) ? srcDescPtr->strides.hStride : 0); + increment_row_ptrs(dstPtrChannels, kernelSize, dstDescPtr->strides.hStride); + } + } + else if ((srcDescPtr->layout == RpptLayout::NCHW) && (dstDescPtr->layout == RpptLayout::NHWC)) + { + /* exclude (2 * padLength) number of columns from alignedLength calculation + since padLength number of columns from the beginning and end of each row will be computed using raw c code */ + Rpp32u alignedLength = ((bufferLength - (2 * padLength)) / 24) * 24; + for(int i = 0; i < roi.xywhROI.roiHeight; i++) + { + int vectorLoopCount = 0; + bool padLengthRows = (i < padLength) ? 1: 0; + T *srcPtrTemp[3][3] = { + {srcPtrRow[0], srcPtrRow[1], srcPtrRow[2]}, + {srcPtrRow[0] + srcDescPtr->strides.cStride, srcPtrRow[1] + srcDescPtr->strides.cStride, srcPtrRow[2] + srcDescPtr->strides.cStride}, + {srcPtrRow[0] + 2 * srcDescPtr->strides.cStride, srcPtrRow[1] + 2 * srcDescPtr->strides.cStride, srcPtrRow[2] + 2 * srcDescPtr->strides.cStride} + }; + + T *dstPtrTemp = dstPtrRow; + // get the number of rows needs to be loaded for the corresponding row + Rpp32s rowKernelLoopLimit = kernelSize; + get_kernel_loop_limit(i, rowKernelLoopLimit, padLength, unpaddedHeight); + + // process padLength number of columns in each row + // left border pixels in image which does not have required pixels in 3x3 box, process them separately + for (int k = 0; k < padLength; k++) + { + for (int c = 0; c < 3; c++) + { + dilate_generic_tensor(srcPtrTemp[c], dstPtrTemp, k, kernelSize, padLength, unpaddedWidth, rowKernelLoopLimit, kernelSizeInverseSquare); + dstPtrTemp++; + } + } +#if __AVX2__ + // process alignedLength number of columns in each row + for (; vectorLoopCount < alignedLength; vectorLoopCount += 24) + { + __m256i pxResultPln[3]; + for (int c = 0; c < 3; c++) + { + __m256i pxRow[3], pxRowHalf[2]; + rpp_load_dilate_char_3x3_host(pxRow, srcPtrTemp[c], rowKernelLoopLimit); + + // unpack lower half and higher half of each of 3 loaded row values from 8 bit to 16 bit and add + unpacklo_and_max_3x3_host(pxRow, &pxRowHalf[0]); + unpackhi_and_max_3x3_host(pxRow, &pxRowHalf[1]); + + // perform blend and shuffle operations for the first 8 output values to get required order and add them + __m128i pxTemp[4]; + extract_4sse_registers(pxRowHalf, pxTemp); + blend_shuffle_max_3x3_host<1, 3>(&pxTemp[0], pxMaskPln, blendRegisterOrder); + blend_shuffle_max_3x3_host<1, 3>(&pxTemp[1], pxMaskPln, blendRegisterOrder); + blend_shuffle_max_3x3_host<1, 3>(&pxTemp[2], pxMaskPln, blendRegisterOrder); + + __m128i pxDst[2]; + pxDst[0] = _mm_packus_epi16(pxTemp[0], pxTemp[1]); + pxDst[1] = _mm_packus_epi16(pxTemp[2], xmm_px0); + + pxResultPln[c] = _mm256_setr_m128i(pxDst[0], pxDst[1]); + increment_row_ptrs(srcPtrTemp[c], kernelSize, 24); + } + if constexpr (std::is_same::value) + { + pxResultPln[0] = _mm256_sub_epi8(pxResultPln[0], avx_pxConvertI8); + pxResultPln[1] = _mm256_sub_epi8(pxResultPln[1], avx_pxConvertI8); + pxResultPln[2] = _mm256_sub_epi8(pxResultPln[2], avx_pxConvertI8); + } + + __m128i pxResultPkd[6]; + // convert result from pln to pkd format and store in output buffer + rpp_convert72_pln3_to_pkd3(pxResultPln, pxResultPkd); + _mm_storeu_si128((__m128i *)dstPtrTemp, pxResultPkd[0]); + _mm_storeu_si128((__m128i *)(dstPtrTemp + 12), pxResultPkd[1]); + _mm_storeu_si128((__m128i *)(dstPtrTemp + 24), pxResultPkd[2]); + _mm_storeu_si128((__m128i *)(dstPtrTemp + 36), pxResultPkd[3]); + _mm_storeu_si128((__m128i *)(dstPtrTemp + 48), pxResultPkd[4]); + _mm_storeu_si128((__m128i *)(dstPtrTemp + 60), pxResultPkd[5]); + dstPtrTemp += 72; + } +#endif + vectorLoopCount += padLength; + for (; vectorLoopCount < bufferLength; vectorLoopCount++) + { + for (int c = 0; c < 3; c++) + { + dilate_generic_tensor(srcPtrTemp[c], dstPtrTemp, vectorLoopCount, kernelSize, padLength, unpaddedWidth, rowKernelLoopLimit, kernelSizeInverseSquare); + increment_row_ptrs(srcPtrTemp[c], kernelSize, 1); + dstPtrTemp++; + } + } + // for the first padLength rows, we need not increment the src row pointers to next rows + increment_row_ptrs(srcPtrRow, kernelSize, (!padLengthRows) ? srcDescPtr->strides.hStride : 0); + dstPtrRow += dstDescPtr->strides.hStride; + } + } + } + else if (kernelSize == 5) + { + T *srcPtrRow[5], *dstPtrRow; + for (int i = 0; i < 5; i++) + srcPtrRow[i] = srcPtrChannel + i * srcDescPtr->strides.hStride; + dstPtrRow = dstPtrChannel; + + // dilate without fused output-layout toggle (NCHW -> NCHW) + if ((srcDescPtr->layout == RpptLayout::NCHW) && (dstDescPtr->layout == RpptLayout::NCHW)) + { + /* exclude (2 * padLength) number of columns from alignedLength calculation + since padLength number of columns from the beginning and end of each row will be computed using raw c code */ + Rpp32u alignedLength = ((bufferLength - (2 * padLength)) / 24) * 24; + for (int c = 0; c < srcDescPtr->c; c++) + { + srcPtrRow[0] = srcPtrChannel; + srcPtrRow[1] = srcPtrRow[0] + srcDescPtr->strides.hStride; + srcPtrRow[2] = srcPtrRow[1] + srcDescPtr->strides.hStride; + srcPtrRow[3] = srcPtrRow[2] + srcDescPtr->strides.hStride; + srcPtrRow[4] = srcPtrRow[3] + srcDescPtr->strides.hStride; + dstPtrRow = dstPtrChannel; + for (int i = 0; i < roi.xywhROI.roiHeight; i++) + { + int vectorLoopCount = 0; + bool padLengthRows = (i < padLength) ? 1 : 0; + T *srcPtrTemp[5] = {srcPtrRow[0], srcPtrRow[1], srcPtrRow[2], srcPtrRow[3], srcPtrRow[4]}; + T *dstPtrTemp = dstPtrRow; + + // get the number of rows needs to be loaded for the corresponding row + Rpp32s rowKernelLoopLimit = kernelSize; + get_kernel_loop_limit(i, rowKernelLoopLimit, padLength, unpaddedHeight); + process_left_border_columns_pln_pln(srcPtrTemp, dstPtrTemp, kernelSize, padLength, unpaddedWidth, rowKernelLoopLimit, kernelSizeInverseSquare); + dstPtrTemp += padLength; +#if __AVX2__ + // process alignedLength number of columns in each row + for (; vectorLoopCount < alignedLength; vectorLoopCount += 24) + { + __m256i pxRow[5], pxRowHalf[2], pxResult; + rpp_load_dilate_char_5x5_host(pxRow, srcPtrTemp, rowKernelLoopLimit); + + // pack lower and higher half of each of 5 loaded row values from 8 bit to 16 bit and add + unpacklo_and_max_5x5_host(pxRow, &pxRowHalf[0]); + unpackhi_and_max_5x5_host(pxRow, &pxRowHalf[1]); + + __m128i pxTemp[4], pxDst[2]; + extract_4sse_registers(pxRowHalf, pxTemp); + blend_shuffle_max_5x5_host<1, 3, 7, 15>(&pxTemp[0], pxMaskPln, blendRegisterOrder); + blend_shuffle_max_5x5_host<1, 3, 7, 15>(&pxTemp[1], pxMaskPln, blendRegisterOrder); + blend_shuffle_max_5x5_host<1, 3, 7, 15>(&pxTemp[2], pxMaskPln, blendRegisterOrder); + pxDst[0] = _mm_packus_epi16(pxTemp[0], pxTemp[1]); + pxDst[1] = _mm_packus_epi16(pxTemp[2], xmm_px0); + pxResult = _mm256_setr_m128i(pxDst[0], pxDst[1]); + if constexpr (std::is_same::value) + pxResult = _mm256_sub_epi8(pxResult, avx_pxConvertI8); + + _mm256_storeu_si256((__m256i *)dstPtrTemp, pxResult); + increment_row_ptrs(srcPtrTemp, kernelSize, 24); + dstPtrTemp += 24; + } +#endif + vectorLoopCount += padLength; + for (; vectorLoopCount < bufferLength; vectorLoopCount++) + { + dilate_generic_tensor(srcPtrTemp, dstPtrTemp, vectorLoopCount, kernelSize, padLength, unpaddedWidth, rowKernelLoopLimit, kernelSizeInverseSquare); + increment_row_ptrs(srcPtrTemp, kernelSize, 1); + dstPtrTemp++; + } + // for the first padLength rows, we need not increment the src row pointers to next rows + increment_row_ptrs(srcPtrRow, kernelSize, (!padLengthRows) ? srcDescPtr->strides.hStride : 0); + dstPtrRow += dstDescPtr->strides.hStride; + } + srcPtrChannel += srcDescPtr->strides.cStride; + dstPtrChannel += dstDescPtr->strides.cStride; + } + } + else if ((srcDescPtr->layout == RpptLayout::NHWC) && (dstDescPtr->layout == RpptLayout::NHWC)) + { + /* exclude ((2 * padLength) * 3) number of columns from alignedLength calculation + since (padLength * 3) number of columns from the beginning and end of each row will be computed using raw c code */ + Rpp32u alignedLength = ((bufferLength - (2 * padLength) * 3) / 18) * 18; + for(int i = 0; i < roi.xywhROI.roiHeight; i++) + { + int vectorLoopCount = 0; + bool padLengthRows = (i < padLength) ? 1: 0; + T *srcPtrTemp[5] = {srcPtrRow[0], srcPtrRow[1], srcPtrRow[2], srcPtrRow[3], srcPtrRow[4]}; + T *dstPtrTemp = dstPtrRow; + + Rpp32s rowKernelLoopLimit = kernelSize; + get_kernel_loop_limit(i, rowKernelLoopLimit, padLength, unpaddedHeight); + process_left_border_columns_pkd_pkd(srcPtrTemp, srcPtrRow, dstPtrTemp, kernelSize, padLength, unpaddedWidth, rowKernelLoopLimit, kernelSizeInverseSquare); + dstPtrTemp += padLength * 3; +#if __AVX2__ + // process remaining columns in each row + for (; vectorLoopCount < alignedLength; vectorLoopCount += 18) + { + __m256i pxRow[5], pxRowHalf[2], pxResult; + rpp_load_dilate_char_5x5_host(pxRow, srcPtrTemp, rowKernelLoopLimit); + + // pack lower and higher half of each of 5 loaded row values from 8 bit to 16 bit and add + unpacklo_and_max_5x5_host(pxRow, &pxRowHalf[0]); + unpackhi_and_max_5x5_host(pxRow, &pxRowHalf[1]); + + __m128i pxTemp[5], pxDst[2]; + extract_4sse_registers(pxRowHalf, pxTemp); + pxTemp[4] = xmm_px0; + blend_shuffle_max_5x5_host<7, 63, 1, 15>(&pxTemp[0], pxMaskPkd, blendRegisterOrder); + blend_shuffle_max_5x5_host<7, 63, 1, 15>(&pxTemp[1], pxMaskPkd, blendRegisterOrder); + blend_shuffle_max_5x5_host<7, 63, 1, 15>(&pxTemp[2], pxMaskPkd, blendRegisterOrder); + pxDst[0] = _mm_packus_epi16(pxTemp[0], pxTemp[1]); + pxDst[1] = _mm_packus_epi16(pxTemp[2], xmm_px0); + pxResult = _mm256_setr_m128i(pxDst[0], pxDst[1]); + if constexpr (std::is_same::value) + pxResult = _mm256_sub_epi8(pxResult, avx_pxConvertI8); + + _mm256_storeu_si256((__m256i *)dstPtrTemp, pxResult); + increment_row_ptrs(srcPtrTemp, kernelSize, 18); + dstPtrTemp += 18; + } +#endif + vectorLoopCount += padLength * 3; + for (; vectorLoopCount < bufferLength; vectorLoopCount++) + { + dilate_generic_tensor(srcPtrTemp, dstPtrTemp, vectorLoopCount / 3, kernelSize, padLength, unpaddedWidth, rowKernelLoopLimit, kernelSizeInverseSquare, 3); + increment_row_ptrs(srcPtrTemp, kernelSize, 1); + dstPtrTemp++; + } + // for the first padLength rows, we need not increment the src row pointers to next rows + increment_row_ptrs(srcPtrRow, kernelSize, (!padLengthRows) ? srcDescPtr->strides.hStride : 0); + dstPtrRow += dstDescPtr->strides.hStride; + } + } + else if ((srcDescPtr->c == 3) && (srcDescPtr->layout == RpptLayout::NHWC) && (dstDescPtr->layout == RpptLayout::NCHW)) + { + /* exclude ((2 * padLength) * 3) number of columns from alignedLength calculation + since (padLength * 3) number of columns from the beginning and end of each row will be computed using raw c code */ + Rpp32u alignedLength = ((bufferLength - (2 * padLength) * 3) / 18) * 18; + T *dstPtrChannels[3]; + for (int i = 0; i < 3; i++) + dstPtrChannels[i] = dstPtrChannel + i * dstDescPtr->strides.cStride; + + for(int i = 0; i < roi.xywhROI.roiHeight; i++) + { + int vectorLoopCount = 0; + bool padLengthRows = (i < padLength) ? 1: 0; + T *srcPtrTemp[5] = {srcPtrRow[0], srcPtrRow[1], srcPtrRow[2], srcPtrRow[3], srcPtrRow[4]}; + T *dstPtrTempChannels[3] = {dstPtrChannels[0], dstPtrChannels[1], dstPtrChannels[2]}; + + Rpp32s rowKernelLoopLimit = kernelSize; + get_kernel_loop_limit(i, rowKernelLoopLimit, padLength, unpaddedHeight); + process_left_border_columns_pkd_pln(srcPtrTemp, srcPtrRow, dstPtrTempChannels, kernelSize, padLength, unpaddedWidth, rowKernelLoopLimit, kernelSizeInverseSquare); +#if __AVX2__ + // process remaining columns in each row + for (; vectorLoopCount < alignedLength; vectorLoopCount += 18) + { + __m256i pxRow[5], pxRowHalf[2]; + rpp_load_dilate_char_5x5_host(pxRow, srcPtrTemp, rowKernelLoopLimit); + + // pack lower and higher half of each of 5 loaded row values from 8 bit to 16 bit and add + unpacklo_and_max_5x5_host(pxRow, &pxRowHalf[0]); + unpackhi_and_max_5x5_host(pxRow, &pxRowHalf[1]); + + __m128i pxTemp[5], pxDst[2]; + extract_4sse_registers(pxRowHalf, pxTemp); + pxTemp[4] = xmm_px0; + blend_shuffle_max_5x5_host<7, 63, 1, 15>(&pxTemp[0], pxMaskPkd, blendRegisterOrder); + blend_shuffle_max_5x5_host<7, 63, 1, 15>(&pxTemp[1], pxMaskPkd, blendRegisterOrder); + blend_shuffle_max_5x5_host<7, 63, 1, 15>(&pxTemp[2], pxMaskPkd, blendRegisterOrder); + pxDst[0] = _mm_packus_epi16(pxTemp[0], pxTemp[1]); + pxDst[1] = _mm_packus_epi16(pxTemp[2], xmm_px0); + if constexpr (std::is_same::value) + { + pxDst[0] = _mm_sub_epi8(pxDst[0], xmm_pxConvertI8); + pxDst[1] = _mm_sub_epi8(pxDst[1], xmm_pxConvertI8); + } + + // convert from PKD3 to PLN3 and store channelwise + __m128i pxDstChn[3]; + rpp_convert24_pkd3_to_pln3(pxDst[0], pxDst[1], pxDstChn); + rpp_storeu_si64((__m128i *)(dstPtrTempChannels[0]), pxDstChn[0]); + rpp_storeu_si64((__m128i *)(dstPtrTempChannels[1]), pxDstChn[1]); + rpp_storeu_si64((__m128i *)(dstPtrTempChannels[2]), pxDstChn[2]); + increment_row_ptrs(srcPtrTemp, kernelSize, 18); + increment_row_ptrs(dstPtrTempChannels, kernelSize, 6); + } +#endif + vectorLoopCount += padLength * 3; + for (int c = 0; vectorLoopCount < bufferLength; vectorLoopCount++, c++) + { + int channel = c % 3; + dilate_generic_tensor(srcPtrTemp, dstPtrTempChannels[channel], vectorLoopCount / 3, kernelSize, padLength, unpaddedWidth, rowKernelLoopLimit, kernelSizeInverseSquare, 3); + increment_row_ptrs(srcPtrTemp, kernelSize, 1); + dstPtrTempChannels[channel]++; + } + // for the first padLength rows, we need not increment the src row pointers to next rows + increment_row_ptrs(srcPtrRow, kernelSize, (!padLengthRows) ? srcDescPtr->strides.hStride : 0); + increment_row_ptrs(dstPtrChannels, kernelSize, dstDescPtr->strides.hStride); + } + } + else if ((srcDescPtr->layout == RpptLayout::NCHW) && (dstDescPtr->layout == RpptLayout::NHWC)) + { + /* exclude (2 * padLength) number of columns from alignedLength calculation + since padLength * 3 number of columns from the beginning and end of each row will be computed using raw c code */ + Rpp32u alignedLength = ((bufferLength - (2 * padLength)) / 24) * 24; + for(int i = 0; i < roi.xywhROI.roiHeight; i++) + { + int vectorLoopCount = 0; + bool padLengthRows = (i < padLength) ? 1: 0; + T *srcPtrTemp[3][5]; + for (int c = 0; c < 3; c++) + { + Rpp32u channelStride = c * srcDescPtr->strides.cStride; + for (int k = 0; k < 5; k++) + srcPtrTemp[c][k] = srcPtrRow[k] + channelStride; + } + T *dstPtrTemp = dstPtrRow; + // get the number of rows needs to be loaded for the corresponding row + Rpp32s rowKernelLoopLimit = kernelSize; + get_kernel_loop_limit(i, rowKernelLoopLimit, padLength, unpaddedHeight); + + // process padLength number of columns in each row + // left border pixels in image which does not have required pixels in 5x5 box, process them separately + for (int k = 0; k < padLength; k++) + { + for (int c = 0; c < 3; c++) + { + dilate_generic_tensor(srcPtrTemp[c], dstPtrTemp, k, kernelSize, padLength, unpaddedWidth, rowKernelLoopLimit, kernelSizeInverseSquare); + dstPtrTemp++; + } + } +#if __AVX2__ + // process alignedLength number of columns in each row + for (; vectorLoopCount < alignedLength; vectorLoopCount += 24) + { + __m256i pxResultPln[3]; + for (int c = 0; c < 3; c++) + { + __m256i pxRow[5], pxRowHalf[2], pxResult; + rpp_load_dilate_char_5x5_host(pxRow, srcPtrTemp[c], rowKernelLoopLimit); + + // pack lower and higher half of each of 5 loaded row values from 8 bit to 16 bit and add + unpacklo_and_max_5x5_host(pxRow, &pxRowHalf[0]); + unpackhi_and_max_5x5_host(pxRow, &pxRowHalf[1]); + + __m128i pxTemp[4], pxDst[2]; + extract_4sse_registers(pxRowHalf, pxTemp); + blend_shuffle_max_5x5_host<1, 3, 7, 15>(&pxTemp[0], pxMaskPln, blendRegisterOrder); + blend_shuffle_max_5x5_host<1, 3, 7, 15>(&pxTemp[1], pxMaskPln, blendRegisterOrder); + blend_shuffle_max_5x5_host<1, 3, 7, 15>(&pxTemp[2], pxMaskPln, blendRegisterOrder); + pxDst[0] = _mm_packus_epi16(pxTemp[0], pxTemp[1]); + pxDst[1] = _mm_packus_epi16(pxTemp[2], xmm_px0); + pxResultPln[c] = _mm256_setr_m128i(pxDst[0], pxDst[1]); + increment_row_ptrs(srcPtrTemp[c], kernelSize, 24); + } + if constexpr (std::is_same::value) + { + pxResultPln[0] = _mm256_sub_epi8(pxResultPln[0], avx_pxConvertI8); + pxResultPln[1] = _mm256_sub_epi8(pxResultPln[1], avx_pxConvertI8); + pxResultPln[2] = _mm256_sub_epi8(pxResultPln[2], avx_pxConvertI8); + } + + __m128i pxResultPkd[6]; + // convert result from pln to pkd format and store in output buffer + rpp_convert72_pln3_to_pkd3(pxResultPln, pxResultPkd); + _mm_storeu_si128((__m128i *)dstPtrTemp, pxResultPkd[0]); + _mm_storeu_si128((__m128i *)(dstPtrTemp + 12), pxResultPkd[1]); + _mm_storeu_si128((__m128i *)(dstPtrTemp + 24), pxResultPkd[2]); + _mm_storeu_si128((__m128i *)(dstPtrTemp + 36), pxResultPkd[3]); + _mm_storeu_si128((__m128i *)(dstPtrTemp + 48), pxResultPkd[4]); + _mm_storeu_si128((__m128i *)(dstPtrTemp + 60), pxResultPkd[5]); + dstPtrTemp += 72; + } +#endif + vectorLoopCount += padLength; + for (; vectorLoopCount < bufferLength; vectorLoopCount++) + { + for (int c = 0; c < 3; c++) + { + dilate_generic_tensor(srcPtrTemp[c], dstPtrTemp, vectorLoopCount, kernelSize, padLength, unpaddedWidth, rowKernelLoopLimit, kernelSizeInverseSquare); + increment_row_ptrs(srcPtrTemp[c], kernelSize, 1); + dstPtrTemp++; + } + } + // for the first padLength rows, we need not increment the src row pointers to next rows + increment_row_ptrs(srcPtrRow, kernelSize, (!padLengthRows) ? srcDescPtr->strides.hStride : 0); + dstPtrRow += dstDescPtr->strides.hStride; + } + } + } + else if (kernelSize == 7) + { + T *srcPtrRow[7], *dstPtrRow; + for (int i = 0; i < 7; i++) + srcPtrRow[i] = srcPtrChannel + i * srcDescPtr->strides.hStride; + dstPtrRow = dstPtrChannel; + + // dilate without fused output-layout toggle (NCHW -> NCHW) + if ((srcDescPtr->layout == RpptLayout::NCHW) && (dstDescPtr->layout == RpptLayout::NCHW)) + { + /* exclude (2 * padLength) number of columns from alignedLength calculation + since padLength number of columns from the beginning and end of each row will be computed using raw c code */ + Rpp32u alignedLength = ((bufferLength - (2 * padLength)) / 24) * 24; + for (int c = 0; c < srcDescPtr->c; c++) + { + srcPtrRow[0] = srcPtrChannel; + for (int k = 1; k < 7; k++) + srcPtrRow[k] = srcPtrRow[k - 1] + srcDescPtr->strides.hStride; + dstPtrRow = dstPtrChannel; + for (int i = 0; i < roi.xywhROI.roiHeight; i++) + { + int vectorLoopCount = 0; + bool padLengthRows = (i < padLength) ? 1 : 0; + T *srcPtrTemp[7]; + for (int k = 0; k < 7; k++) + srcPtrTemp[k] = srcPtrRow[k]; + T *dstPtrTemp = dstPtrRow; + + // get the number of rows needs to be loaded for the corresponding row + Rpp32s rowKernelLoopLimit = kernelSize; + get_kernel_loop_limit(i, rowKernelLoopLimit, padLength, unpaddedHeight); + process_left_border_columns_pln_pln(srcPtrTemp, dstPtrTemp, kernelSize, padLength, unpaddedWidth, rowKernelLoopLimit, kernelSizeInverseSquare); + dstPtrTemp += padLength; +#if __AVX2__ + // process alignedLength number of columns in each row + for (; vectorLoopCount < alignedLength; vectorLoopCount += 24) + { + __m256i pxRow[7], pxRowHalf[2], pxResult; + rpp_load_dilate_char_7x7_host(pxRow, srcPtrTemp, rowKernelLoopLimit); + + // unpack lower and higher half of each of 7 loaded row values from 8 bit to 16 bit and add + unpacklo_and_max_7x7_host(pxRow, &pxRowHalf[0]); + unpackhi_and_max_7x7_host(pxRow, &pxRowHalf[1]); + + __m128i pxTemp[4], pxDst[2]; + extract_4sse_registers(pxRowHalf, pxTemp); + blend_shuffle_max_7x7_host<1, 3, 7, 15, 31, 63>(&pxTemp[0], pxMaskPln, blendRegisterOrder); + blend_shuffle_max_7x7_host<1, 3, 7, 15, 31, 63>(&pxTemp[1], pxMaskPln, blendRegisterOrder); + blend_shuffle_max_7x7_host<1, 3, 7, 15, 31, 63>(&pxTemp[2], pxMaskPln, blendRegisterOrder); + pxDst[0] = _mm_packus_epi16(pxTemp[0], pxTemp[1]); + pxDst[1] = _mm_packus_epi16(pxTemp[2], xmm_px0); + pxResult = _mm256_setr_m128i(pxDst[0], pxDst[1]); + if constexpr (std::is_same::value) + pxResult = _mm256_sub_epi8(pxResult, avx_pxConvertI8); + + _mm256_storeu_si256((__m256i *)dstPtrTemp, pxResult); + increment_row_ptrs(srcPtrTemp, kernelSize, 24); + dstPtrTemp += 24; + } +#endif + vectorLoopCount += padLength; + for (; vectorLoopCount < bufferLength; vectorLoopCount++) + { + dilate_generic_tensor(srcPtrTemp, dstPtrTemp, vectorLoopCount, kernelSize, padLength, unpaddedWidth, rowKernelLoopLimit, kernelSizeInverseSquare); + increment_row_ptrs(srcPtrTemp, kernelSize, 1); + dstPtrTemp++; + } + // for the first padLength rows, we need not increment the src row pointers to next rows + increment_row_ptrs(srcPtrRow, kernelSize, (!padLengthRows) ? srcDescPtr->strides.hStride : 0); + dstPtrRow += dstDescPtr->strides.hStride; + } + srcPtrChannel += srcDescPtr->strides.cStride; + dstPtrChannel += dstDescPtr->strides.cStride; + } + } + else if ((srcDescPtr->layout == RpptLayout::NCHW) && (dstDescPtr->layout == RpptLayout::NHWC)) + { + /* exclude (2 * padLength) number of columns from alignedLength calculation + since padLength number of columns from the beginning and end of each row will be computed using raw c code */ + Rpp32u alignedLength = ((bufferLength - (2 * padLength)) / 24) * 24; + for(int i = 0; i < roi.xywhROI.roiHeight; i++) + { + int vectorLoopCount = 0; + bool padLengthRows = (i < padLength) ? 1: 0; + T *srcPtrTemp[3][7]; + for (int c = 0; c < 3; c++) + { + Rpp32u channelStride = c * srcDescPtr->strides.cStride; + for (int k = 0; k < 7; k++) + srcPtrTemp[c][k] = srcPtrRow[k] + channelStride; + } + T *dstPtrTemp = dstPtrRow; + // get the number of rows needs to be loaded for the corresponding row + Rpp32s rowKernelLoopLimit = kernelSize; + get_kernel_loop_limit(i, rowKernelLoopLimit, padLength, unpaddedHeight); + + // process padLength number of columns in each row + // left border pixels in image which does not have required pixels in 7x7 box, process them separately + for (int k = 0; k < padLength; k++) + { + for (int c = 0; c < 3; c++) + { + dilate_generic_tensor(srcPtrTemp[c], dstPtrTemp, k, kernelSize, padLength, unpaddedWidth, rowKernelLoopLimit, kernelSizeInverseSquare); + dstPtrTemp++; + } + } +#if __AVX2__ + // process alignedLength number of columns in each row + for (; vectorLoopCount < alignedLength; vectorLoopCount += 24) + { + __m256i pxResultPln[3]; + for (int c = 0; c < 3; c++) + { + __m256i pxRow[7], pxRowHalf[2], pxResult; + rpp_load_dilate_char_7x7_host(pxRow, srcPtrTemp[c], rowKernelLoopLimit); + + // unpack lower and higher half of each of 7 loaded row values from 8 bit to 16 bit and add + unpacklo_and_max_7x7_host(pxRow, &pxRowHalf[0]); + unpackhi_and_max_7x7_host(pxRow, &pxRowHalf[1]); + + __m128i pxTemp[4], pxDst[2]; + extract_4sse_registers(pxRowHalf, pxTemp); + blend_shuffle_max_7x7_host<1, 3, 7, 15, 31, 63>(&pxTemp[0], pxMaskPln, blendRegisterOrder); + blend_shuffle_max_7x7_host<1, 3, 7, 15, 31, 63>(&pxTemp[1], pxMaskPln, blendRegisterOrder); + blend_shuffle_max_7x7_host<1, 3, 7, 15, 31, 63>(&pxTemp[2], pxMaskPln, blendRegisterOrder); + pxDst[0] = _mm_packus_epi16(pxTemp[0], pxTemp[1]); + pxDst[1] = _mm_packus_epi16(pxTemp[2], xmm_px0); + pxResultPln[c] = _mm256_setr_m128i(pxDst[0], pxDst[1]); + increment_row_ptrs(srcPtrTemp[c], kernelSize, 24); + } + if constexpr (std::is_same::value) + { + pxResultPln[0] = _mm256_sub_epi8(pxResultPln[0], avx_pxConvertI8); + pxResultPln[1] = _mm256_sub_epi8(pxResultPln[1], avx_pxConvertI8); + pxResultPln[2] = _mm256_sub_epi8(pxResultPln[2], avx_pxConvertI8); + } + + __m128i pxResultPkd[6]; + // convert result from pln to pkd format and store in output buffer + rpp_convert72_pln3_to_pkd3(pxResultPln, pxResultPkd); + _mm_storeu_si128((__m128i *)dstPtrTemp, pxResultPkd[0]); + _mm_storeu_si128((__m128i *)(dstPtrTemp + 12), pxResultPkd[1]); + _mm_storeu_si128((__m128i *)(dstPtrTemp + 24), pxResultPkd[2]); + _mm_storeu_si128((__m128i *)(dstPtrTemp + 36), pxResultPkd[3]); + _mm_storeu_si128((__m128i *)(dstPtrTemp + 48), pxResultPkd[4]); + _mm_storeu_si128((__m128i *)(dstPtrTemp + 60), pxResultPkd[5]); + dstPtrTemp += 72; + } +#endif + vectorLoopCount += padLength; + for (; vectorLoopCount < bufferLength; vectorLoopCount++) + { + for (int c = 0; c < 3; c++) + { + dilate_generic_tensor(srcPtrTemp[c], dstPtrTemp, vectorLoopCount, kernelSize, padLength, unpaddedWidth, rowKernelLoopLimit, kernelSizeInverseSquare); + increment_row_ptrs(srcPtrTemp[c], kernelSize, 1); + dstPtrTemp++; + } + } + // for the first padLength rows, we need not increment the src row pointers to next rows + increment_row_ptrs(srcPtrRow, kernelSize, (!padLengthRows) ? srcDescPtr->strides.hStride : 0); + dstPtrRow += dstDescPtr->strides.hStride; + } + } + else if ((srcDescPtr->layout == RpptLayout::NHWC) && (dstDescPtr->layout == RpptLayout::NHWC)) + { + /* exclude ((2 * padLength) * 3) number of columns from alignedLength calculation + since (padLength * 3) number of columns from the beginning and end of each row will be computed using raw c code */ + Rpp32u alignedLength = ((bufferLength - 2 * padLength * 3) / 12) * 12; + for(int i = 0; i < roi.xywhROI.roiHeight; i++) + { + int vectorLoopCount = 0; + bool padLengthRows = (i < padLength) ? 1: 0; + T *srcPtrTemp[7]; + for (int k = 0; k < 7; k++) + srcPtrTemp[k] = srcPtrRow[k]; + T *dstPtrTemp = dstPtrRow; + + Rpp32s rowKernelLoopLimit = kernelSize; + get_kernel_loop_limit(i, rowKernelLoopLimit, padLength, unpaddedHeight); + process_left_border_columns_pkd_pkd(srcPtrTemp, srcPtrRow, dstPtrTemp, kernelSize, padLength, unpaddedWidth, rowKernelLoopLimit, kernelSizeInverseSquare); + dstPtrTemp += padLength * 3; +#if __AVX2__ + // process remaining columns in each row + for (; vectorLoopCount < alignedLength; vectorLoopCount += 12) + { + __m256i pxRow[7], pxRowHalf[2]; + rpp_load_dilate_char_7x7_host(pxRow, srcPtrTemp, rowKernelLoopLimit); + + // unpack lower and higher half of each of 7 loaded row values from 8 bit to 16 bit and add + unpacklo_and_max_7x7_host(pxRow, &pxRowHalf[0]); + unpackhi_and_max_7x7_host(pxRow, &pxRowHalf[1]); + + __m128i pxTemp[4], pxResult; + extract_4sse_registers(pxRowHalf, pxTemp); + blend_shuffle_max_7x7_host<7, 63, 1, 15, 127, 3>(&pxTemp[0], pxMaskPkd, blendRegisterOrder); + blend_shuffle_max_7x7_host<7, 63, 1, 15, 127, 3>(&pxTemp[1], pxMaskPkd, blendRegisterOrder); + pxResult = _mm_packus_epi16(pxTemp[0], pxTemp[1]); + if constexpr (std::is_same::value) + pxResult = _mm_sub_epi8(pxResult, xmm_pxConvertI8); + + _mm_storeu_si128((__m128i*)dstPtrTemp, pxResult); + increment_row_ptrs(srcPtrTemp, kernelSize, 12); + dstPtrTemp += 12; + } +#endif + vectorLoopCount += padLength * 3; + for (; vectorLoopCount < bufferLength; vectorLoopCount++) + { + dilate_generic_tensor(srcPtrTemp, dstPtrTemp, vectorLoopCount / 3, kernelSize, padLength, unpaddedWidth, rowKernelLoopLimit, kernelSizeInverseSquare, 3); + increment_row_ptrs(srcPtrTemp, kernelSize, 1); + dstPtrTemp++; + } + // for the first padLength rows, we need not increment the src row pointers to next rows + increment_row_ptrs(srcPtrRow, kernelSize, (!padLengthRows) ? srcDescPtr->strides.hStride : 0); + dstPtrRow += dstDescPtr->strides.hStride; + } + } + else if ((srcDescPtr->c == 3) && (srcDescPtr->layout == RpptLayout::NHWC) && (dstDescPtr->layout == RpptLayout::NCHW)) + { + /* exclude ((2 * padLength) * 3) number of columns from alignedLength calculation + since (padLength * 3) number of columns from the beginning and end of each row will be computed using raw c code */ + Rpp32u alignedLength = ((bufferLength - 2 * padLength * 3) / 12) * 12; + T *dstPtrChannels[3]; + for (int i = 0; i < 3; i++) + dstPtrChannels[i] = dstPtrChannel + i * dstDescPtr->strides.cStride; + + for(int i = 0; i < roi.xywhROI.roiHeight; i++) + { + int vectorLoopCount = 0; + bool padLengthRows = (i < padLength) ? 1: 0; + T *srcPtrTemp[7] = {srcPtrRow[0], srcPtrRow[1], srcPtrRow[2], srcPtrRow[3], srcPtrRow[4], srcPtrRow[5], srcPtrRow[6]}; + T *dstPtrTempChannels[3] = {dstPtrChannels[0], dstPtrChannels[1], dstPtrChannels[2]}; + + Rpp32s rowKernelLoopLimit = kernelSize; + get_kernel_loop_limit(i, rowKernelLoopLimit, padLength, unpaddedHeight); + process_left_border_columns_pkd_pln(srcPtrTemp, srcPtrRow, dstPtrTempChannels, kernelSize, padLength, unpaddedWidth, rowKernelLoopLimit, kernelSizeInverseSquare); +#if __AVX2__ + // process remaining columns in each row + for (; vectorLoopCount < alignedLength; vectorLoopCount += 12) + { + __m256i pxRow[7], pxRowHalf[2]; + rpp_load_dilate_char_7x7_host(pxRow, srcPtrTemp, rowKernelLoopLimit); + + // unpack lower and higher half of each of 7 loaded row values from 8 bit to 16 bit and add + unpacklo_and_max_7x7_host(pxRow, &pxRowHalf[0]); + unpackhi_and_max_7x7_host(pxRow, &pxRowHalf[1]); + + __m128i pxTemp[4], pxResult[2]; + extract_4sse_registers(pxRowHalf, pxTemp); + blend_shuffle_max_7x7_host<7, 63, 1, 15, 127, 3>(&pxTemp[0], pxMaskPkd, blendRegisterOrder); + blend_shuffle_max_7x7_host<7, 63, 1, 15, 127, 3>(&pxTemp[1], pxMaskPkd, blendRegisterOrder); + pxResult[0] = _mm_packus_epi16(pxTemp[0], pxTemp[1]); + pxResult[1] = xmm_px0; + if constexpr (std::is_same::value) + pxResult[0] = _mm_sub_epi8(pxResult[0], xmm_pxConvertI8); + + // convert from PKD3 to PLN3 and store channelwise + __m128i pxDstChn[3]; + rpp_convert24_pkd3_to_pln3(pxResult[0], pxResult[1], pxDstChn); + rpp_storeu_si64((__m128i *)(dstPtrTempChannels[0]), pxDstChn[0]); + rpp_storeu_si64((__m128i *)(dstPtrTempChannels[1]), pxDstChn[1]); + rpp_storeu_si64((__m128i *)(dstPtrTempChannels[2]), pxDstChn[2]); + increment_row_ptrs(srcPtrTemp, kernelSize, 12); + increment_row_ptrs(dstPtrTempChannels, kernelSize, 4); + } +#endif + vectorLoopCount += padLength * 3; + for (int c = 0; vectorLoopCount < bufferLength; vectorLoopCount++, c++) + { + int channel = c % 3; + dilate_generic_tensor(srcPtrTemp, dstPtrTempChannels[channel], vectorLoopCount / 3, kernelSize, padLength, unpaddedWidth, rowKernelLoopLimit, kernelSizeInverseSquare, 3); + increment_row_ptrs(srcPtrTemp, kernelSize, 1); + dstPtrTempChannels[channel]++; + } + // for the first padLength rows, we need not increment the src row pointers to next rows + increment_row_ptrs(srcPtrRow, kernelSize, (!padLengthRows) ? srcDescPtr->strides.hStride : 0); + increment_row_ptrs(dstPtrChannels, kernelSize, dstDescPtr->strides.hStride); + } + } + } + else if (kernelSize == 9) + { + T *srcPtrRow[9], *dstPtrRow; + for (int i = 0; i < 9; i++) + srcPtrRow[i] = srcPtrChannel + i * srcDescPtr->strides.hStride; + dstPtrRow = dstPtrChannel; + if ((srcDescPtr->layout == RpptLayout::NCHW) && (dstDescPtr->layout == RpptLayout::NCHW)) + { + /* exclude (2 * padLength) number of columns from alignedLength calculation + since padLength number of columns from the beginning and end of each row will be computed using raw c code */ + Rpp32u alignedLength = ((bufferLength - (2 * padLength)) / 16) * 16; + for (int c = 0; c < srcDescPtr->c; c++) + { + srcPtrRow[0] = srcPtrChannel; + for (int k = 1; k < 9; k++) + srcPtrRow[k] = srcPtrRow[k - 1] + srcDescPtr->strides.hStride; + dstPtrRow = dstPtrChannel; + for(int i = 0; i < roi.xywhROI.roiHeight; i++) + { + int vectorLoopCount = 0; + bool padLengthRows = (i < padLength) ? 1: 0; + T *srcPtrTemp[9]; + for (int k = 0; k < 9; k++) + srcPtrTemp[k] = srcPtrRow[k]; + T *dstPtrTemp = dstPtrRow; + + Rpp32s rowKernelLoopLimit = kernelSize; + get_kernel_loop_limit(i, rowKernelLoopLimit, padLength, unpaddedHeight); + process_left_border_columns_pln_pln(srcPtrTemp, dstPtrTemp, kernelSize, padLength, unpaddedWidth, rowKernelLoopLimit, kernelSizeInverseSquare); + dstPtrTemp += padLength; +#if __AVX2__ + // process alignedLength number of columns in each row + for (; vectorLoopCount < alignedLength; vectorLoopCount += 16) + { + __m256i pxRow[9], pxRowHalf[2]; + rpp_load_dilate_char_9x9_host(pxRow, srcPtrTemp, rowKernelLoopLimit); + + // unpack lower half and higher half of each of 9 loaded row values from 8 bit to 16 bit and add + unpacklo_and_max_9x9_host(pxRow, &pxRowHalf[0]); + unpackhi_and_max_9x9_host(pxRow, &pxRowHalf[1]); + + __m128i pxTemp[3], pxDst; + extract_3sse_registers(pxRowHalf, pxTemp); + blend_shuffle_max_9x9_host<1, 3, 7, 15, 31, 63, 127>(&pxTemp[0], pxMaskPln, blendRegisterOrder); + blend_shuffle_max_9x9_host<1, 3, 7, 15, 31, 63, 127>(&pxTemp[1], pxMaskPln, blendRegisterOrder); + pxDst = _mm_packus_epi16(pxTemp[0], pxTemp[1]); + if constexpr (std::is_same::value) + pxDst = _mm_sub_epi8(pxDst, xmm_pxConvertI8); + + _mm_storeu_si128((__m128i *)dstPtrTemp, pxDst); + increment_row_ptrs(srcPtrTemp, kernelSize, 16); + dstPtrTemp += 16; + } +#endif + vectorLoopCount += padLength; + for (; vectorLoopCount < bufferLength; vectorLoopCount++) + { + dilate_generic_tensor(srcPtrTemp, dstPtrTemp, vectorLoopCount, kernelSize, padLength, unpaddedWidth, rowKernelLoopLimit, kernelSizeInverseSquare); + increment_row_ptrs(srcPtrTemp, kernelSize, 1); + dstPtrTemp++; + } + // for the first padLength rows, we need not increment the src row pointers to next rows + increment_row_ptrs(srcPtrRow, kernelSize, (!padLengthRows) ? srcDescPtr->strides.hStride : 0); + dstPtrRow += dstDescPtr->strides.hStride; + } + srcPtrChannel += srcDescPtr->strides.cStride; + dstPtrChannel += dstDescPtr->strides.cStride; + } + } + else if ((srcDescPtr->c == 3) && (srcDescPtr->layout == RpptLayout::NHWC) && (dstDescPtr->layout == RpptLayout::NHWC)) + { + /* exclude ((2 * padLength) * 3) number of columns from alignedLength calculation + since (padLength * 3) number of columns from the beginning and end of each row will be computed using raw c code */ + Rpp32u alignedLength = ((bufferLength - (2 * padLength) * 3) / 64) * 64; + for(int i = 0; i < roi.xywhROI.roiHeight; i++) + { + int vectorLoopCount = 0; + bool padLengthRows = (i < padLength) ? 1: 0; + T *srcPtrTemp[9]; + for (int k = 0; k < 9; k++) + srcPtrTemp[k] = srcPtrRow[k]; + T *dstPtrTemp = dstPtrRow; + + Rpp32s rowKernelLoopLimit = kernelSize; + get_kernel_loop_limit(i, rowKernelLoopLimit, padLength, unpaddedHeight); + process_left_border_columns_pkd_pkd(srcPtrTemp, srcPtrRow, dstPtrTemp, kernelSize, padLength, unpaddedWidth, rowKernelLoopLimit, kernelSizeInverseSquare); + dstPtrTemp += padLength * 3; +#if __AVX2__ + // load first 32 elements elements + __m256i pxRow[9]; + if (alignedLength) + rpp_load_dilate_char_9x9_host(pxRow, srcPtrTemp, rowKernelLoopLimit); + + // process alignedLength number of columns in each row + for (; vectorLoopCount < alignedLength; vectorLoopCount += 32) + { + __m256i pxRowHalf[2], pxResult; + unpacklo_and_max_9x9_host(pxRow, &pxRowHalf[0]); + unpackhi_and_max_9x9_host(pxRow, &pxRowHalf[1]); + + // get the accumalated result for first 8 elements + __m128i px128[8], pxTemp[7], pxDst[2]; + extract_4sse_registers(pxRowHalf, &px128[0]); + blend_shuffle_max_9x9_host<7, 63, 1, 15, 127, 3, 31>(&px128[0], pxMaskPkd, blendRegisterOrder); + + // compute for next 8 elements + increment_row_ptrs(srcPtrTemp, kernelSize, 32); + rpp_load_dilate_char_9x9_host(pxRow, srcPtrTemp, rowKernelLoopLimit); + unpacklo_and_max_9x9_host(pxRow, &pxRowHalf[0]); + unpackhi_and_max_9x9_host(pxRow, &pxRowHalf[1]); + + // get the accumalated result for next 24 elements + extract_4sse_registers(pxRowHalf, &px128[4]); + blend_shuffle_max_9x9_host<7, 63, 1, 15, 127, 3, 31>(&px128[1], pxMaskPkd, blendRegisterOrder); + blend_shuffle_max_9x9_host<7, 63, 1, 15, 127, 3, 31>(&px128[2], pxMaskPkd, blendRegisterOrder); + blend_shuffle_max_9x9_host<7, 63, 1, 15, 127, 3, 31>(&px128[3], pxMaskPkd, blendRegisterOrder); + + // compute final result + pxDst[0] = _mm_packus_epi16(px128[0], px128[1]); + pxDst[1] = _mm_packus_epi16(px128[2], px128[3]); + pxResult = _mm256_setr_m128i(pxDst[0], pxDst[1]); + if constexpr (std::is_same::value) + pxResult = _mm256_sub_epi8(pxResult, avx_pxConvertI8); + + _mm256_storeu_si256((__m256i *)dstPtrTemp, pxResult); + dstPtrTemp += 32; + } +#endif + vectorLoopCount += padLength * 3; + for (; vectorLoopCount < bufferLength; vectorLoopCount++) + { + dilate_generic_tensor(srcPtrTemp, dstPtrTemp, vectorLoopCount / 3, kernelSize, padLength, unpaddedWidth, rowKernelLoopLimit, kernelSizeInverseSquare, 3); + increment_row_ptrs(srcPtrTemp, kernelSize, 1); + dstPtrTemp++; + } + // for the first padLength rows, we need not increment the src row pointers to next rows + increment_row_ptrs(srcPtrRow, kernelSize, (!padLengthRows) ? srcDescPtr->strides.hStride : 0); + dstPtrRow += dstDescPtr->strides.hStride; + } + } + else if ((srcDescPtr->layout == RpptLayout::NCHW) && (dstDescPtr->layout == RpptLayout::NHWC)) + { + /* exclude (2 * padLength) number of columns from alignedLength calculation + since padLength number of columns from the beginning and end of each row will be computed using raw c code */ + Rpp32u alignedLength = ((bufferLength - (2 * padLength)) / 16) * 16; + for(int i = 0; i < roi.xywhROI.roiHeight; i++) + { + int vectorLoopCount = 0; + bool padLengthRows = (i < padLength) ? 1: 0; + T *srcPtrTemp[3][9]; + for (int c = 0; c < 3; c++) + { + Rpp32u channelStride = c * srcDescPtr->strides.cStride; + for (int k = 0; k < 9; k++) + srcPtrTemp[c][k] = srcPtrRow[k] + channelStride; + } + T *dstPtrTemp = dstPtrRow; + + Rpp32s rowKernelLoopLimit = kernelSize; + get_kernel_loop_limit(i, rowKernelLoopLimit, padLength, unpaddedHeight); + + // process padLength number of columns in each row + for (int k = 0; k < padLength; k++) + { + for (int c = 0; c < 3; c++) + { + dilate_generic_tensor(srcPtrTemp[c], dstPtrTemp, k, kernelSize, padLength, unpaddedWidth, rowKernelLoopLimit, kernelSizeInverseSquare); + dstPtrTemp++; + } + } +#if __AVX2__ + // process alignedLength number of columns in each row + for (; vectorLoopCount < alignedLength; vectorLoopCount += 16) + { + __m128i pxResultPln[3]; + for (int c = 0; c < 3; c++) + { + __m256i pxRow[9], pxRowHalf[2]; + rpp_load_dilate_char_9x9_host(pxRow, srcPtrTemp[c], rowKernelLoopLimit); + + // unpack lower half and higher half of each of 9 loaded row values from 8 bit to 16 bit and add + unpacklo_and_max_9x9_host(pxRow, &pxRowHalf[0]); + unpackhi_and_max_9x9_host(pxRow, &pxRowHalf[1]); + + __m128i pxTemp[3], pxDst; + extract_3sse_registers(pxRowHalf, pxTemp); + blend_shuffle_max_9x9_host<1, 3, 7, 15, 31, 63, 127>(&pxTemp[0], pxMaskPln, blendRegisterOrder); + blend_shuffle_max_9x9_host<1, 3, 7, 15, 31, 63, 127>(&pxTemp[1], pxMaskPln, blendRegisterOrder); + pxResultPln[c] = _mm_packus_epi16(pxTemp[0], pxTemp[1]); + increment_row_ptrs(srcPtrTemp[c], kernelSize, 16); + } + if constexpr (std::is_same::value) + { + pxResultPln[0] = _mm_sub_epi8(pxResultPln[0], xmm_pxConvertI8); + pxResultPln[1] = _mm_sub_epi8(pxResultPln[1], xmm_pxConvertI8); + pxResultPln[2] = _mm_sub_epi8(pxResultPln[2], xmm_pxConvertI8); + } + + __m128i pxResultPkd[4]; + rpp_convert48_pln3_to_pkd3(pxResultPln, pxResultPkd); + _mm_storeu_si128((__m128i *)(dstPtrTemp), pxResultPkd[0]); + _mm_storeu_si128((__m128i *)(dstPtrTemp + 12), pxResultPkd[1]); + _mm_storeu_si128((__m128i *)(dstPtrTemp + 24), pxResultPkd[2]); + _mm_storeu_si128((__m128i *)(dstPtrTemp + 36), pxResultPkd[3]); + dstPtrTemp += 48; + } +#endif + vectorLoopCount += padLength; + for (; vectorLoopCount < bufferLength; vectorLoopCount++) + { + for (int c = 0; c < srcDescPtr->c; c++) + { + dilate_generic_tensor(srcPtrTemp[c], dstPtrTemp, vectorLoopCount, kernelSize, padLength, unpaddedWidth, rowKernelLoopLimit, kernelSizeInverseSquare); + increment_row_ptrs(srcPtrTemp[c], kernelSize, 1); + dstPtrTemp++; + } + } + // for the first padLength rows, we need not increment the src row pointers to next rows + increment_row_ptrs(srcPtrRow, kernelSize, (!padLengthRows) ? srcDescPtr->strides.hStride : 0); + dstPtrRow += dstDescPtr->strides.hStride; + } + } + else if ((srcDescPtr->layout == RpptLayout::NHWC) && (dstDescPtr->layout == RpptLayout::NCHW)) + { + /* exclude ((2 * padLength) * 3) number of columns from alignedLength calculation + since (padLength * 3) number of columns from the beginning and end of each row will be computed using raw c code */ + Rpp32u alignedLength = ((bufferLength - (2 * padLength) * 3) / 64) * 64; + T *dstPtrChannels[3]; + for (int c = 0; c < 3; c++) + dstPtrChannels[c] = dstPtrChannel + c * dstDescPtr->strides.cStride; + for(int i = 0; i < roi.xywhROI.roiHeight; i++) + { + int vectorLoopCount = 0; + bool padLengthRows = (i < padLength) ? 1: 0; + T *srcPtrTemp[9]; + for (int k = 0; k < 9; k++) + srcPtrTemp[k] = srcPtrRow[k]; + T *dstPtrTempChannels[3] = {dstPtrChannels[0], dstPtrChannels[1], dstPtrChannels[2]}; + + Rpp32s rowKernelLoopLimit = kernelSize; + get_kernel_loop_limit(i, rowKernelLoopLimit, padLength, unpaddedHeight); + process_left_border_columns_pkd_pln(srcPtrTemp, srcPtrRow, dstPtrTempChannels, kernelSize, padLength, unpaddedWidth, rowKernelLoopLimit, kernelSizeInverseSquare); +#if __AVX2__ + // process alignedLength number of columns in each row + for (; vectorLoopCount < alignedLength; vectorLoopCount += 24) + { + // load first 32 elements elements + __m256i pxRow[9], pxRowHalf[2]; + rpp_load_dilate_char_9x9_host(pxRow, srcPtrTemp, rowKernelLoopLimit); + + // get the accumalated result for first 8 elements + unpacklo_and_max_9x9_host(pxRow, &pxRowHalf[0]); + unpackhi_and_max_9x9_host(pxRow, &pxRowHalf[1]); + + // get the accumalated result for first 8 elements + __m128i px128[8], pxTemp[7], pxDst[2]; + extract_4sse_registers(pxRowHalf, &px128[0]); + blend_shuffle_max_9x9_host<7, 63, 1, 15, 127, 3, 31>(&px128[0], pxMaskPkd, blendRegisterOrder); + + // compute for next 8 elements + increment_row_ptrs(srcPtrTemp, kernelSize, 32); + rpp_load_dilate_char_9x9_host(pxRow, srcPtrTemp, rowKernelLoopLimit); + unpacklo_and_max_9x9_host(pxRow, &pxRowHalf[0]); + unpackhi_and_max_9x9_host(pxRow, &pxRowHalf[1]); + + // get the accumalated result for next 24 elements + extract_4sse_registers(pxRowHalf, &px128[4]); + blend_shuffle_max_9x9_host<7, 63, 1, 15, 127, 3, 31>(&px128[1], pxMaskPkd, blendRegisterOrder); + blend_shuffle_max_9x9_host<7, 63, 1, 15, 127, 3, 31>(&px128[2], pxMaskPkd, blendRegisterOrder); + blend_shuffle_max_9x9_host<7, 63, 1, 15, 127, 3, 31>(&px128[3], pxMaskPkd, blendRegisterOrder); + pxDst[0] = _mm_packus_epi16(px128[0], px128[1]); + pxDst[1] = _mm_packus_epi16(px128[2], px128[3]); + if constexpr (std::is_same::value) + { + pxDst[0] = _mm_sub_epi8(pxDst[0], xmm_pxConvertI8); + pxDst[1] = _mm_sub_epi8(pxDst[1], xmm_pxConvertI8); + } + + // convert from PKD3 to PLN3 and store + __m128i pxDstChn[3]; + rpp_convert24_pkd3_to_pln3(pxDst[0], pxDst[1], pxDstChn); + rpp_storeu_si64((__m128i *)(dstPtrTempChannels[0]), pxDstChn[0]); + rpp_storeu_si64((__m128i *)(dstPtrTempChannels[1]), pxDstChn[1]); + rpp_storeu_si64((__m128i *)(dstPtrTempChannels[2]), pxDstChn[2]); + increment_row_ptrs(srcPtrTemp, kernelSize, -8); + increment_row_ptrs(dstPtrTempChannels, 3, 8); + } +#endif + vectorLoopCount += padLength * 3; + for (; vectorLoopCount < bufferLength; vectorLoopCount++) + { + int channel = vectorLoopCount % 3; + dilate_generic_tensor(srcPtrTemp, dstPtrTempChannels[channel], vectorLoopCount / 3, kernelSize, padLength, unpaddedWidth, rowKernelLoopLimit, kernelSizeInverseSquare, 3); + increment_row_ptrs(srcPtrTemp, kernelSize, 1); + dstPtrTempChannels[channel]++; + } + // for the first padLength rows, we need not increment the src row pointers to next rows + increment_row_ptrs(srcPtrRow, kernelSize, (!padLengthRows) ? srcDescPtr->strides.hStride : 0); + increment_row_ptrs(dstPtrChannels, 3, dstDescPtr->strides.hStride); + } + } + } + } + + return RPP_SUCCESS; +} + +// F32 and F16 bitdepth +template +RppStatus dilate_float_host_tensor(T *srcPtr, + RpptDescPtr srcDescPtr, + T *dstPtr, + RpptDescPtr dstDescPtr, + Rpp32u kernelSize, + RpptROIPtr roiTensorPtrSrc, + RpptRoiType roiType, + RppLayoutParams layoutParams, + rpp::Handle& handle) +{ + RpptROI roiDefault = {0, 0, (Rpp32s)srcDescPtr->w, (Rpp32s)srcDescPtr->h}; + Rpp32u numThreads = handle.GetNumThreads(); + static_assert((std::is_same::value || std::is_same::value), "T must be Rpp32f or Rpp16f"); + + if ((kernelSize != 3) && (kernelSize != 5) && (kernelSize != 7) && (kernelSize != 9)) + return dilate_generic_host_tensor(srcPtr, srcDescPtr, dstPtr, dstDescPtr, kernelSize, roiTensorPtrSrc, roiType, layoutParams, handle); + + // set the required masks array needed for permute operations +#if __AVX2__ + __m256i pxMaskPln[7] = {avx_pxMaskRotate0To1, avx_pxMaskRotate0To2, avx_pxMaskRotate0To3, avx_pxMaskRotate0To4, avx_pxMaskRotate0To5, avx_pxMaskRotate0To6, avx_pxMaskRotate0To7}; + __m256i pxMaskPkd[7] = {avx_pxMaskRotate0To3, avx_pxMaskRotate0To6, avx_pxMaskRotate0To1, avx_pxMaskRotate0To4, avx_pxMaskRotate0To7, avx_pxMaskRotate0To2, avx_pxMaskRotate0To5}; +#endif + + omp_set_dynamic(0); +#pragma omp parallel for num_threads(numThreads) + for(int batchCount = 0; batchCount < dstDescPtr->n; batchCount++) + { + RpptROI roi; + RpptROIPtr roiPtrInput = &roiTensorPtrSrc[batchCount]; + compute_roi_validation_host(roiPtrInput, &roi, &roiDefault, roiType); + + T *srcPtrImage, *dstPtrImage; + srcPtrImage = srcPtr + batchCount * srcDescPtr->strides.nStride; + dstPtrImage = dstPtr + batchCount * dstDescPtr->strides.nStride; + + Rpp32u padLength = kernelSize / 2; + Rpp32u bufferLength = roi.xywhROI.roiWidth * layoutParams.bufferMultiplier; + Rpp32u unpaddedHeight = roi.xywhROI.roiHeight - padLength; + Rpp32u unpaddedWidth = roi.xywhROI.roiWidth - padLength; + Rpp32f kernelSizeInverseSquare = 1.0 / (kernelSize * kernelSize); +#if __AVX2__ + const __m256 pConvolutionFactor = _mm256_set1_ps(kernelSizeInverseSquare); + // set the register order needed for blend operations + Rpp32u blendRegisterOrder[7] = {0, 0, 1, 1, 1, 2, 2}; + if (srcDescPtr->layout == RpptLayout::NCHW) + std::fill_n(blendRegisterOrder, 7, 0); +#endif + + T *srcPtrChannel, *dstPtrChannel; + srcPtrChannel = srcPtrImage + (roi.xywhROI.xy.y * srcDescPtr->strides.hStride) + (roi.xywhROI.xy.x * layoutParams.bufferMultiplier); + dstPtrChannel = dstPtrImage; + if (kernelSize == 3) + { + T *srcPtrRow[3], *dstPtrRow; + for (int i = 0; i < 3; i++) + srcPtrRow[i] = srcPtrChannel + i * srcDescPtr->strides.hStride; + dstPtrRow = dstPtrChannel; + + // dilate without fused output-layout toggle (NCHW -> NCHW) + if ((srcDescPtr->layout == RpptLayout::NCHW) && (dstDescPtr->layout == RpptLayout::NCHW)) + { + /* exclude (2 * padLength) number of columns from alignedLength calculation + since padLength number of columns from the beginning and end of each row will be computed using raw c code */ + Rpp32u alignedLength = ((bufferLength - (2 * padLength)) / 16) * 16; + for (int c = 0; c < srcDescPtr->c; c++) + { + srcPtrRow[0] = srcPtrChannel; + srcPtrRow[1] = srcPtrRow[0] + srcDescPtr->strides.hStride; + srcPtrRow[2] = srcPtrRow[1] + srcDescPtr->strides.hStride; + dstPtrRow = dstPtrChannel; + for(int i = 0; i < roi.xywhROI.roiHeight; i++) + { + int vectorLoopCount = 0; + bool padLengthRows = (i < padLength) ? 1: 0; + T *srcPtrTemp[3] = {srcPtrRow[0], srcPtrRow[1], srcPtrRow[2]}; + T *dstPtrTemp = dstPtrRow; + + // get the number of rows needs to be loaded for the corresponding row + Rpp32s rowKernelLoopLimit = kernelSize; + get_kernel_loop_limit(i, rowKernelLoopLimit, padLength, unpaddedHeight); + process_left_border_columns_pln_pln(srcPtrTemp, dstPtrTemp, kernelSize, padLength, unpaddedWidth, rowKernelLoopLimit, kernelSizeInverseSquare); + dstPtrTemp += padLength; +#if __AVX2__ + // process alignedLength number of columns in each row + for (; vectorLoopCount < alignedLength; vectorLoopCount += 14) + { + __m256 pRow[3], pTemp[3], pDst[2]; + rpp_load_dilate_float_3x3_host(pRow, srcPtrTemp, rowKernelLoopLimit); + max_rows_3x3(pRow, &pTemp[0]); + + increment_row_ptrs(srcPtrTemp, kernelSize, 8); + rpp_load_dilate_float_3x3_host(pRow, srcPtrTemp, rowKernelLoopLimit); + max_rows_3x3(pRow, &pTemp[1]); + pTemp[2] = avx_p0; + + blend_permute_max_3x3_host<1, 3>(&pTemp[0], &pDst[0], pConvolutionFactor, pxMaskPln, blendRegisterOrder); + blend_permute_max_3x3_host<1, 3>(&pTemp[1], &pDst[1], pConvolutionFactor, pxMaskPln, blendRegisterOrder); + rpp_store16_float(dstPtrTemp, pDst); + + increment_row_ptrs(srcPtrTemp, kernelSize, 6); + dstPtrTemp += 14; + } +#endif + vectorLoopCount += padLength; + for (; vectorLoopCount < bufferLength; vectorLoopCount++) + { + dilate_generic_tensor(srcPtrTemp, dstPtrTemp, vectorLoopCount, kernelSize, padLength, unpaddedWidth, rowKernelLoopLimit, kernelSizeInverseSquare); + increment_row_ptrs(srcPtrTemp, kernelSize, 1); + dstPtrTemp++; + } + // for the first padLength rows, we need not increment the src row pointers to next rows + increment_row_ptrs(srcPtrRow, kernelSize, (!padLengthRows) ? srcDescPtr->strides.hStride : 0); + dstPtrRow += dstDescPtr->strides.hStride; + } + srcPtrChannel += srcDescPtr->strides.cStride; + dstPtrChannel += dstDescPtr->strides.cStride; + } + } + else if ((srcDescPtr->layout == RpptLayout::NHWC) && (dstDescPtr->layout == RpptLayout::NHWC)) + { + /* exclude ((2 * padLength) * 3) number of columns from alignedLength calculation + since (padLength * 3) number of columns from the beginning and end of each row will be computed using raw c code */ + Rpp32u alignedLength = ((bufferLength - (2 * padLength) * 3) / 24) * 24; + for(int i = 0; i < roi.xywhROI.roiHeight; i++) + { + int vectorLoopCount = 0; + bool padLengthRows = (i < padLength) ? 1: 0; + T *srcPtrTemp[3] = {srcPtrRow[0], srcPtrRow[1], srcPtrRow[2]}; + T *dstPtrTemp = dstPtrRow; + + Rpp32s rowKernelLoopLimit = kernelSize; + get_kernel_loop_limit(i, rowKernelLoopLimit, padLength, unpaddedHeight); + process_left_border_columns_pkd_pkd(srcPtrTemp, srcPtrRow, dstPtrTemp, kernelSize, padLength, unpaddedWidth, rowKernelLoopLimit, kernelSizeInverseSquare); + dstPtrTemp += padLength * 3; +#if __AVX2__ + // process remaining columns in each row + for (; vectorLoopCount < alignedLength; vectorLoopCount += 16) + { + __m256 pRow[3], pTemp[3], pDst[2]; + rpp_load_dilate_float_3x3_host(pRow, srcPtrTemp, rowKernelLoopLimit); + max_rows_3x3(pRow, &pTemp[0]); + + increment_row_ptrs(srcPtrTemp, kernelSize, 8); + rpp_load_dilate_float_3x3_host(pRow, srcPtrTemp, rowKernelLoopLimit); + max_rows_3x3(pRow, &pTemp[1]); + + increment_row_ptrs(srcPtrTemp, kernelSize, 8); + rpp_load_dilate_float_3x3_host(pRow, srcPtrTemp, rowKernelLoopLimit); + max_rows_3x3(pRow, &pTemp[2]); + + blend_permute_max_3x3_host<7, 63>(&pTemp[0], &pDst[0], pConvolutionFactor, pxMaskPkd, blendRegisterOrder); + blend_permute_max_3x3_host<7, 63>(&pTemp[1], &pDst[1], pConvolutionFactor, pxMaskPkd, blendRegisterOrder); + + rpp_store16_float(dstPtrTemp, pDst); + dstPtrTemp += 16; + } +#endif + vectorLoopCount += padLength * 3; + for (; vectorLoopCount < bufferLength; vectorLoopCount++) + { + dilate_generic_tensor(srcPtrTemp, dstPtrTemp, vectorLoopCount / 3, kernelSize, padLength, unpaddedWidth, rowKernelLoopLimit, kernelSizeInverseSquare, 3); + increment_row_ptrs(srcPtrTemp, kernelSize, 1); + dstPtrTemp++; + } + // for the first padLength rows, we need not increment the src row pointers to next rows + increment_row_ptrs(srcPtrRow, kernelSize, (!padLengthRows) ? srcDescPtr->strides.hStride : 0); + dstPtrRow += dstDescPtr->strides.hStride; + } + } + else if ((srcDescPtr->c == 3) && (srcDescPtr->layout == RpptLayout::NHWC) && (dstDescPtr->layout == RpptLayout::NCHW)) + { + /* exclude ((2 * padLength) * 3) number of columns from alignedLength calculation + since (padLength * 3) number of columns from the beginning and end of each row will be computed using raw c code */ + Rpp32u alignedLength = ((bufferLength - (2 * padLength) * 3) / 24) * 24; + T *dstPtrChannels[3]; + for (int i = 0; i < 3; i++) + dstPtrChannels[i] = dstPtrChannel + i * dstDescPtr->strides.cStride; + for(int i = 0; i < roi.xywhROI.roiHeight; i++) + { + int vectorLoopCount = 0; + bool padLengthRows = (i < padLength) ? 1: 0; + T *srcPtrTemp[3] = {srcPtrRow[0], srcPtrRow[1], srcPtrRow[2]}; + T *dstPtrTempChannels[3] = {dstPtrChannels[0], dstPtrChannels[1], dstPtrChannels[2]}; + + Rpp32s rowKernelLoopLimit = kernelSize; + get_kernel_loop_limit(i, rowKernelLoopLimit, padLength, unpaddedHeight); + process_left_border_columns_pkd_pln(srcPtrTemp, srcPtrRow, dstPtrTempChannels, kernelSize, padLength, unpaddedWidth, rowKernelLoopLimit, kernelSizeInverseSquare); +#if __AVX2__ + // process remaining columns in each row + for (; vectorLoopCount < alignedLength; vectorLoopCount += 12) + { + __m256 pRow[3], pTemp[3], pDst[2]; + rpp_load_dilate_float_3x3_host(pRow, srcPtrTemp, rowKernelLoopLimit); + max_rows_3x3(pRow, &pTemp[0]); + + increment_row_ptrs(srcPtrTemp, kernelSize, 8); + rpp_load_dilate_float_3x3_host(pRow, srcPtrTemp, rowKernelLoopLimit); + max_rows_3x3(pRow, &pTemp[1]); + + increment_row_ptrs(srcPtrTemp, kernelSize, 8); + rpp_load_dilate_float_3x3_host(pRow, srcPtrTemp, rowKernelLoopLimit); + max_rows_3x3(pRow, &pTemp[2]); + + blend_permute_max_3x3_host<7, 63>(&pTemp[0], &pDst[0], pConvolutionFactor, pxMaskPkd, blendRegisterOrder); + blend_permute_max_3x3_host<7, 63>(&pTemp[1], &pDst[1], pConvolutionFactor, pxMaskPkd, blendRegisterOrder); + + __m128 pDstPln[3]; + rpp_convert12_f32pkd3_to_f32pln3(pDst, pDstPln); + rpp_store12_float_pkd_pln(dstPtrTempChannels, pDstPln); + + increment_row_ptrs(srcPtrTemp, kernelSize, -4); + increment_row_ptrs(dstPtrTempChannels, kernelSize, 4); + } +#endif + vectorLoopCount += padLength * 3; + for (int c = 0; vectorLoopCount < bufferLength; vectorLoopCount++, c++) + { + int channel = c % 3; + dilate_generic_tensor(srcPtrTemp, dstPtrTempChannels[channel], vectorLoopCount / 3, kernelSize, padLength, unpaddedWidth, rowKernelLoopLimit, kernelSizeInverseSquare, 3); + increment_row_ptrs(srcPtrTemp, kernelSize, 1); + dstPtrTempChannels[channel]++; + } + // for the first padLength rows, we need not increment the src row pointers to next rows + increment_row_ptrs(srcPtrRow, kernelSize, (!padLengthRows) ? srcDescPtr->strides.hStride : 0); + increment_row_ptrs(dstPtrChannels, kernelSize, dstDescPtr->strides.hStride); + } + } + else if ((srcDescPtr->c == 3) && (srcDescPtr->layout == RpptLayout::NCHW) && (dstDescPtr->layout == RpptLayout::NHWC)) + { + /* exclude (2 * padLength) number of columns from alignedLength calculation + since padLength number of columns from the beginning and end of each row will be computed using raw c code */ + Rpp32u alignedLength = ((bufferLength - (2 * padLength)) / 16) * 16; + for(int i = 0; i < roi.xywhROI.roiHeight; i++) + { + int vectorLoopCount = 0; + bool padLengthRows = (i < padLength) ? 1: 0; + T *srcPtrTemp[3][3] = { + {srcPtrRow[0], srcPtrRow[1], srcPtrRow[2]}, + {srcPtrRow[0] + srcDescPtr->strides.cStride, srcPtrRow[1] + srcDescPtr->strides.cStride, srcPtrRow[2] + srcDescPtr->strides.cStride}, + {srcPtrRow[0] + 2 * srcDescPtr->strides.cStride, srcPtrRow[1] + 2 * srcDescPtr->strides.cStride, srcPtrRow[2] + 2 * srcDescPtr->strides.cStride} + }; + + T *dstPtrTemp = dstPtrRow; + // get the number of rows needs to be loaded for the corresponding row + Rpp32s rowKernelLoopLimit = kernelSize; + get_kernel_loop_limit(i, rowKernelLoopLimit, padLength, unpaddedHeight); + + // process padLength number of columns in each row + // left border pixels in image which does not have required pixels in 3x3 box, process them separately + for (int k = 0; k < padLength; k++) + { + for (int c = 0; c < 3; c++) + { + dilate_generic_tensor(srcPtrTemp[c], dstPtrTemp, k, kernelSize, padLength, unpaddedWidth, rowKernelLoopLimit, kernelSizeInverseSquare); + dstPtrTemp++; + } + } +#if __AVX2__ + // process alignedLength number of columns in each row + for (; vectorLoopCount < alignedLength; vectorLoopCount += 14) + { + __m256 pResult[6]; + for (int c = 0; c < 3; c++) + { + int channelStride = c * 2; + __m256 pRow[3], pTemp[3]; + rpp_load_dilate_float_3x3_host(pRow, srcPtrTemp[c], rowKernelLoopLimit); + max_rows_3x3(pRow, &pTemp[0]); + + increment_row_ptrs(srcPtrTemp[c], kernelSize, 8); + rpp_load_dilate_float_3x3_host(pRow, srcPtrTemp[c], rowKernelLoopLimit); + max_rows_3x3(pRow, &pTemp[1]); + pTemp[2] = avx_p0; + + blend_permute_max_3x3_host<1, 3>(&pTemp[0], &pResult[channelStride], pConvolutionFactor, pxMaskPln, blendRegisterOrder); + blend_permute_max_3x3_host<1, 3>(&pTemp[1], &pResult[channelStride + 1], pConvolutionFactor, pxMaskPln, blendRegisterOrder); + increment_row_ptrs(srcPtrTemp[c], kernelSize, 6); + } + + // convert result from pln to pkd format and store in output buffer + if constexpr (std::is_same::value) + rpp_simd_store(rpp_store48_f32pln3_to_f32pkd3_avx, dstPtrTemp, pResult); + else if constexpr (std::is_same::value) + rpp_simd_store(rpp_store48_f32pln3_to_f16pkd3_avx, dstPtrTemp, pResult); + + dstPtrTemp += 42; + } +#endif + vectorLoopCount += padLength; + for (; vectorLoopCount < bufferLength; vectorLoopCount++) + { + for (int c = 0; c < 3; c++) + { + dilate_generic_tensor(srcPtrTemp[c], dstPtrTemp, vectorLoopCount, kernelSize, padLength, unpaddedWidth, rowKernelLoopLimit, kernelSizeInverseSquare); + increment_row_ptrs(srcPtrTemp[c], kernelSize, 1); + dstPtrTemp++; + } + } + // for the first padLength rows, we need not increment the src row pointers to next rows + increment_row_ptrs(srcPtrRow, kernelSize, (!padLengthRows) ? srcDescPtr->strides.hStride : 0); + dstPtrRow += dstDescPtr->strides.hStride; + } + } + } + else if (kernelSize == 5) + { + T *srcPtrRow[5], *dstPtrRow; + for (int i = 0; i < 5; i++) + srcPtrRow[i] = srcPtrChannel + i * srcDescPtr->strides.hStride; + dstPtrRow = dstPtrChannel; + + // dilate without fused output-layout toggle (NCHW -> NCHW) + if ((srcDescPtr->layout == RpptLayout::NCHW) && (dstDescPtr->layout == RpptLayout::NCHW)) + { + /* exclude (2 * padLength) number of columns from alignedLength calculation + since padLength number of columns from the beginning and end of each row will be computed using raw c code */ + Rpp32u alignedLength = ((bufferLength - (2 * padLength)) / 16) * 16; + for (int c = 0; c < srcDescPtr->c; c++) + { + srcPtrRow[0] = srcPtrChannel; + for (int k = 1; k < 5; k++) + srcPtrRow[k] = srcPtrRow[k - 1] + srcDescPtr->strides.hStride; + + dstPtrRow = dstPtrChannel; + for(int i = 0; i < roi.xywhROI.roiHeight; i++) + { + int vectorLoopCount = 0; + bool padLengthRows = (i < padLength) ? 1: 0; + T *srcPtrTemp[5] = {srcPtrRow[0], srcPtrRow[1], srcPtrRow[2], srcPtrRow[3], srcPtrRow[4]}; + T *dstPtrTemp = dstPtrRow; + + // get the number of rows needs to be loaded for the corresponding row + Rpp32s rowKernelLoopLimit = kernelSize; + get_kernel_loop_limit(i, rowKernelLoopLimit, padLength, unpaddedHeight); + process_left_border_columns_pln_pln(srcPtrTemp, dstPtrTemp, kernelSize, padLength, unpaddedWidth, rowKernelLoopLimit, kernelSizeInverseSquare); + dstPtrTemp += padLength; +#if __AVX2__ + // process alignedLength number of columns in each row + for (; vectorLoopCount < alignedLength; vectorLoopCount += 12) + { + __m256 pRow[5], pDst[2], pTemp[3]; + rpp_load_dilate_float_5x5_host(pRow, srcPtrTemp, rowKernelLoopLimit); + max_rows_5x5(pRow, &pTemp[0]); + + increment_row_ptrs(srcPtrTemp, kernelSize, 8); + rpp_load_dilate_float_5x5_host(pRow, srcPtrTemp, rowKernelLoopLimit); + max_rows_5x5(pRow, &pTemp[1]); + pTemp[2] = avx_p0; + + blend_permute_max_5x5_host<1, 3, 7, 15>(&pTemp[0], &pDst[0], pConvolutionFactor, pxMaskPln, blendRegisterOrder); + blend_permute_max_5x5_host<1, 3, 7, 15>(&pTemp[1], &pDst[1], pConvolutionFactor, pxMaskPln, blendRegisterOrder); + + rpp_store16_float(dstPtrTemp, pDst); + increment_row_ptrs(srcPtrTemp, kernelSize, 4); + dstPtrTemp += 12; + } +#endif + vectorLoopCount += padLength; + for (; vectorLoopCount < bufferLength; vectorLoopCount++) + { + dilate_generic_tensor(srcPtrTemp, dstPtrTemp, vectorLoopCount, kernelSize, padLength, unpaddedWidth, rowKernelLoopLimit, kernelSizeInverseSquare); + increment_row_ptrs(srcPtrTemp, kernelSize, 1); + dstPtrTemp++; + } + // for the first padLength rows, we need not increment the src row pointers to next rows + increment_row_ptrs(srcPtrRow, kernelSize, (!padLengthRows) ? srcDescPtr->strides.hStride : 0); + dstPtrRow += dstDescPtr->strides.hStride; + } + srcPtrChannel += srcDescPtr->strides.cStride; + dstPtrChannel += dstDescPtr->strides.cStride; + } + } + else if ((srcDescPtr->layout == RpptLayout::NHWC) && (dstDescPtr->layout == RpptLayout::NHWC)) + { + /* exclude ((2 * padLength) * 3) number of columns from alignedLength calculation + since (padLength * 3) number of columns from the beginning and end of each row will be computed using raw c code */ + Rpp32u alignedLength = ((bufferLength - (2 * padLength * 3)) / 24) * 24; + for(int i = 0; i < roi.xywhROI.roiHeight; i++) + { + int vectorLoopCount = 0; + bool padLengthRows = (i < padLength) ? 1: 0; + T *srcPtrTemp[5]; + for (int k = 0; k < 5; k++) + srcPtrTemp[k] = srcPtrRow[k]; + T *dstPtrTemp = dstPtrRow; + + Rpp32s rowKernelLoopLimit = kernelSize; + get_kernel_loop_limit(i, rowKernelLoopLimit, padLength, unpaddedHeight); + process_left_border_columns_pkd_pkd(srcPtrTemp, srcPtrRow, dstPtrTemp, kernelSize, padLength, unpaddedWidth, rowKernelLoopLimit, kernelSizeInverseSquare); + dstPtrTemp += padLength * 3; +#if __AVX2__ + // process remaining columns in each row + for (; vectorLoopCount < alignedLength; vectorLoopCount += 12) + { + // compute max of loaded values from 9 rows + __m256 pRow[5], pDst[2], pTemp[4]; + rpp_load_dilate_float_5x5_host(pRow, srcPtrTemp, rowKernelLoopLimit); + max_rows_5x5(pRow, &pTemp[0]); + + increment_row_ptrs(srcPtrTemp, kernelSize, 8); + rpp_load_dilate_float_5x5_host(pRow, srcPtrTemp, rowKernelLoopLimit); + max_rows_5x5(pRow, &pTemp[1]); + + increment_row_ptrs(srcPtrTemp, kernelSize, 8); + rpp_load_dilate_float_5x5_host(pRow, srcPtrTemp, rowKernelLoopLimit); + max_rows_5x5(pRow, &pTemp[2]); + pTemp[3] = avx_p0; + + blend_permute_max_5x5_host<7, 63, 1, 15>(&pTemp[0], &pDst[0], pConvolutionFactor, pxMaskPkd, blendRegisterOrder); + blend_permute_max_5x5_host<7, 63, 1, 15>(&pTemp[1], &pDst[1], pConvolutionFactor, pxMaskPkd, blendRegisterOrder); + + rpp_store16_float(dstPtrTemp, pDst); + increment_row_ptrs(srcPtrTemp, kernelSize, -4); + dstPtrTemp += 12; + } +#endif + vectorLoopCount += padLength * 3; + for (; vectorLoopCount < bufferLength; vectorLoopCount++) + { + dilate_generic_tensor(srcPtrTemp, dstPtrTemp, vectorLoopCount / 3, kernelSize, padLength, unpaddedWidth, rowKernelLoopLimit, kernelSizeInverseSquare, 3); + increment_row_ptrs(srcPtrTemp, kernelSize, 1); + dstPtrTemp++; + } + // for the first padLength rows, we need not increment the src row pointers to next rows + increment_row_ptrs(srcPtrRow, kernelSize, (!padLengthRows) ? srcDescPtr->strides.hStride : 0); + dstPtrRow += dstDescPtr->strides.hStride; + } + } + else if ((srcDescPtr->c == 3) && (srcDescPtr->layout == RpptLayout::NCHW) && (dstDescPtr->layout == RpptLayout::NHWC)) + { + /* exclude (2 * padLength) number of columns from alignedLength calculation + since padLength number of columns from the beginning and end of each row will be computed using raw c code */ + Rpp32u alignedLength = ((bufferLength - (2 * padLength)) / 16) * 16; + for(int i = 0; i < roi.xywhROI.roiHeight; i++) + { + int vectorLoopCount = 0; + bool padLengthRows = (i < padLength) ? 1: 0; + T *srcPtrTemp[3][5]; + for (int c = 0; c < 3; c++) + { + Rpp32u channelStride = c * srcDescPtr->strides.cStride; + for (int k = 0; k < 5; k++) + srcPtrTemp[c][k] = srcPtrRow[k] + channelStride; + } + T *dstPtrTemp = dstPtrRow; + + // get the number of rows needs to be loaded for the corresponding row + Rpp32s rowKernelLoopLimit = kernelSize; + get_kernel_loop_limit(i, rowKernelLoopLimit, padLength, unpaddedHeight); + + // process padLength number of columns in each row + for (int k = 0; k < padLength; k++) + { + for (int c = 0; c < 3; c++) + { + dilate_generic_tensor(srcPtrTemp[c], dstPtrTemp, k, kernelSize, padLength, unpaddedWidth, rowKernelLoopLimit, kernelSizeInverseSquare); + dstPtrTemp++; + } + } +#if __AVX2__ + // process alignedLength number of columns in each row + for (; vectorLoopCount < alignedLength; vectorLoopCount += 8) + { + __m256 pResultPln[3]; + for (int c = 0; c < 3; c++) + { + __m256 pRow[5], pTemp[2]; + rpp_load_dilate_float_5x5_host(pRow, srcPtrTemp[c], rowKernelLoopLimit); + max_rows_5x5(pRow, &pTemp[0]); + + increment_row_ptrs(srcPtrTemp[c], kernelSize, 8); + rpp_load_dilate_float_5x5_host(pRow, srcPtrTemp[c], rowKernelLoopLimit); + max_rows_5x5(pRow, &pTemp[1]); + blend_permute_max_5x5_host<1, 3, 7, 15>(pTemp, &pResultPln[c], pConvolutionFactor, pxMaskPln, blendRegisterOrder); + } + + // convert result from pln to pkd format and store in output buffer + if constexpr (std::is_same::value) + rpp_simd_store(rpp_store24_f32pln3_to_f32pkd3_avx, dstPtrTemp, pResultPln); + else if constexpr (std::is_same::value) + rpp_simd_store(rpp_store24_f32pln3_to_f16pkd3_avx, dstPtrTemp, pResultPln); + + dstPtrTemp += 24; + } +#endif + vectorLoopCount += padLength; + for (; vectorLoopCount < bufferLength; vectorLoopCount++) + { + for (int c = 0; c < srcDescPtr->c; c++) + { + dilate_generic_tensor(srcPtrTemp[c], dstPtrTemp, vectorLoopCount, kernelSize, padLength, unpaddedWidth, rowKernelLoopLimit, kernelSizeInverseSquare); + increment_row_ptrs(srcPtrTemp[c], kernelSize, 1); + dstPtrTemp++; + } + } + // for the first padLength rows, we need not increment the src row pointers to next rows + increment_row_ptrs(srcPtrRow, kernelSize, (!padLengthRows) ? srcDescPtr->strides.hStride : 0); + dstPtrRow += dstDescPtr->strides.hStride; + } + } + else if ((srcDescPtr->c == 3) && (srcDescPtr->layout == RpptLayout::NHWC) && (dstDescPtr->layout == RpptLayout::NCHW)) + { + /* exclude ((2 * padLength) * 3) number of columns from alignedLength calculation + since (padLength * 3) number of columns from the beginning and end of each row will be computed using raw c code */ + Rpp32u alignedLength = ((bufferLength - (2 * padLength) * 3) / 24) * 24; + T *dstPtrChannels[3]; + for (int i = 0; i < 3; i++) + dstPtrChannels[i] = dstPtrChannel + i * dstDescPtr->strides.cStride; + for(int i = 0; i < roi.xywhROI.roiHeight; i++) + { + int vectorLoopCount = 0; + bool padLengthRows = (i < padLength) ? 1: 0; + T *srcPtrTemp[5]; + for (int k = 0; k < 5; k++) + srcPtrTemp[k] = srcPtrRow[k]; + T *dstPtrTempChannels[3] = {dstPtrChannels[0], dstPtrChannels[1], dstPtrChannels[2]}; + + Rpp32s rowKernelLoopLimit = kernelSize; + get_kernel_loop_limit(i, rowKernelLoopLimit, padLength, unpaddedHeight); + process_left_border_columns_pkd_pln(srcPtrTemp, srcPtrRow, dstPtrTempChannels, kernelSize, padLength, unpaddedWidth, rowKernelLoopLimit, kernelSizeInverseSquare); +#if __AVX2__ + // process remaining columns in each row + for (; vectorLoopCount < alignedLength; vectorLoopCount += 12) + { + // compute max of loaded values from 9 rows + __m256 pRow[5], pDst[2], pTemp[4]; + rpp_load_dilate_float_5x5_host(pRow, srcPtrTemp, rowKernelLoopLimit); + max_rows_5x5(pRow, &pTemp[0]); + + increment_row_ptrs(srcPtrTemp, kernelSize, 8); + rpp_load_dilate_float_5x5_host(pRow, srcPtrTemp, rowKernelLoopLimit); + max_rows_5x5(pRow, &pTemp[1]); + + increment_row_ptrs(srcPtrTemp, kernelSize, 8); + rpp_load_dilate_float_5x5_host(pRow, srcPtrTemp, rowKernelLoopLimit); + max_rows_5x5(pRow, &pTemp[2]); + pTemp[3] = avx_p0; + + blend_permute_max_5x5_host<7, 63, 1, 15>(&pTemp[0], &pDst[0], pConvolutionFactor, pxMaskPkd, blendRegisterOrder); + blend_permute_max_5x5_host<7, 63, 1, 15>(&pTemp[1], &pDst[1], pConvolutionFactor, pxMaskPkd, blendRegisterOrder); + + __m128 pDstPln[3]; + rpp_convert12_f32pkd3_to_f32pln3(pDst, pDstPln); + rpp_store12_float_pkd_pln(dstPtrTempChannels, pDstPln); + + increment_row_ptrs(srcPtrTemp, kernelSize, -4); + increment_row_ptrs(dstPtrTempChannels, 3, 4); + } +#endif + vectorLoopCount += padLength * 3; + for (; vectorLoopCount < bufferLength; vectorLoopCount++) + { + int channel = vectorLoopCount % 3; + dilate_generic_tensor(srcPtrTemp, dstPtrTempChannels[channel], vectorLoopCount / 3, kernelSize, padLength, unpaddedWidth, rowKernelLoopLimit, kernelSizeInverseSquare, 3); + increment_row_ptrs(srcPtrTemp, kernelSize, 1); + dstPtrTempChannels[channel]++; + } + // for the first padLength rows, we need not increment the src row pointers to next rows + increment_row_ptrs(srcPtrRow, kernelSize, (!padLengthRows) ? srcDescPtr->strides.hStride : 0); + increment_row_ptrs(dstPtrChannels, 3, dstDescPtr->strides.hStride); + } + } + } + else if (kernelSize == 7) + { + T *srcPtrRow[7], *dstPtrRow; + for (int i = 0; i < 7; i++) + srcPtrRow[i] = srcPtrChannel + i * srcDescPtr->strides.hStride; + dstPtrRow = dstPtrChannel; + + // dilate without fused output-layout toggle (NCHW -> NCHW) + if ((srcDescPtr->layout == RpptLayout::NCHW) && (dstDescPtr->layout == RpptLayout::NCHW)) + { + /* exclude (2 * padLength) number of columns from alignedLength calculation + since padLength number of columns from the beginning and end of each row will be computed using raw c code */ + Rpp32u alignedLength = ((bufferLength - (2 * padLength)) / 16) * 16; + for (int c = 0; c < srcDescPtr->c; c++) + { + srcPtrRow[0] = srcPtrChannel; + for (int k = 1; k < 7; k++) + srcPtrRow[k] = srcPtrRow[k - 1] + srcDescPtr->strides.hStride; + + dstPtrRow = dstPtrChannel; + for(int i = 0; i < roi.xywhROI.roiHeight; i++) + { + int vectorLoopCount = 0; + bool padLengthRows = (i < padLength) ? 1: 0; + T *srcPtrTemp[7]; + for (int k = 0; k < 7; k++) + srcPtrTemp[k] = srcPtrRow[k]; + T *dstPtrTemp = dstPtrRow; + + // get the number of rows needs to be loaded for the corresponding row + Rpp32s rowKernelLoopLimit = kernelSize; + get_kernel_loop_limit(i, rowKernelLoopLimit, padLength, unpaddedHeight); + process_left_border_columns_pln_pln(srcPtrTemp, dstPtrTemp, kernelSize, padLength, unpaddedWidth, rowKernelLoopLimit, kernelSizeInverseSquare); + dstPtrTemp += padLength; +#if __AVX2__ + // process alignedLength number of columns in each row + for (; vectorLoopCount < alignedLength; vectorLoopCount += 8) + { + __m256 pRow[7], pTemp[2], pDst; + rpp_load_dilate_float_7x7_host(pRow, srcPtrTemp, rowKernelLoopLimit); + max_rows_7x7(pRow, &pTemp[0]); + + increment_row_ptrs(srcPtrTemp, kernelSize, 8); + rpp_load_dilate_float_7x7_host(pRow, srcPtrTemp, rowKernelLoopLimit); + max_rows_7x7(pRow, &pTemp[1]); + blend_permute_max_7x7_host<1, 3, 7, 15, 31, 63>(&pTemp[0], &pDst, pConvolutionFactor, pxMaskPln, blendRegisterOrder); + + // convert result from pln to pkd format and store in output buffer + if constexpr (std::is_same::value) + _mm256_storeu_ps(dstPtrTemp, pDst); + else if constexpr (std::is_same::value) + _mm_storeu_si128((__m128i *)dstPtrTemp, _mm256_cvtps_ph(pDst, _MM_FROUND_TO_ZERO | _MM_FROUND_NO_EXC)); + + dstPtrTemp += 8; + } +#endif + vectorLoopCount += padLength; + for (; vectorLoopCount < bufferLength; vectorLoopCount++) + { + dilate_generic_tensor(srcPtrTemp, dstPtrTemp, vectorLoopCount, kernelSize, padLength, unpaddedWidth, rowKernelLoopLimit, kernelSizeInverseSquare); + increment_row_ptrs(srcPtrTemp, kernelSize, 1); + dstPtrTemp++; + } + // for the first padLength rows, we need not increment the src row pointers to next rows + increment_row_ptrs(srcPtrRow, kernelSize, (!padLengthRows) ? srcDescPtr->strides.hStride : 0); + dstPtrRow += dstDescPtr->strides.hStride; + } + srcPtrChannel += srcDescPtr->strides.cStride; + dstPtrChannel += dstDescPtr->strides.cStride; + } + } + else if ((srcDescPtr->layout == RpptLayout::NHWC) && (dstDescPtr->layout == RpptLayout::NHWC)) + { + /* exclude ((2 * padLength) * 3) number of columns from alignedLength calculation + since (padLength * 3) number of columns from the beginning and end of each row will be computed using raw c code */ + Rpp32u alignedLength = ((bufferLength - (2 * padLength) * 3) / 32) * 32; + for(int i = 0; i < roi.xywhROI.roiHeight; i++) + { + int vectorLoopCount = 0; + bool padLengthRows = (i < padLength) ? 1: 0; + T *srcPtrTemp[7]; + for (int k = 0; k < 7; k++) + srcPtrTemp[k] = srcPtrRow[k]; + T *dstPtrTemp = dstPtrRow; + + Rpp32s rowKernelLoopLimit = kernelSize; + get_kernel_loop_limit(i, rowKernelLoopLimit, padLength, unpaddedHeight); + process_left_border_columns_pkd_pkd(srcPtrTemp, srcPtrRow, dstPtrTemp, kernelSize, padLength, unpaddedWidth, rowKernelLoopLimit, kernelSizeInverseSquare); + dstPtrTemp += padLength * 3; +#if __AVX2__ + __m256 pRow[7], pTemp[4]; + if (alignedLength) + { + rpp_load_dilate_float_7x7_host(pRow, srcPtrTemp, rowKernelLoopLimit); + max_rows_7x7(pRow, &pTemp[0]); + + increment_row_ptrs(srcPtrTemp, kernelSize, 8); + rpp_load_dilate_float_7x7_host(pRow, srcPtrTemp, rowKernelLoopLimit); + max_rows_7x7(pRow, &pTemp[1]); + + increment_row_ptrs(srcPtrTemp, kernelSize, 8); + rpp_load_dilate_float_7x7_host(pRow, srcPtrTemp, rowKernelLoopLimit); + max_rows_7x7(pRow, &pTemp[2]); + } + + // process remaining columns in each row + for (; vectorLoopCount < alignedLength; vectorLoopCount += 8) + { + // compute max of loaded values from 7 rows + increment_row_ptrs(srcPtrTemp, kernelSize, 8); + rpp_load_dilate_float_7x7_host(pRow, srcPtrTemp, rowKernelLoopLimit); + max_rows_7x7(pRow, &pTemp[3]); + + __m256 pDst; + blend_permute_max_7x7_host<7, 63, 1, 15, 127, 3>(pTemp, &pDst, pConvolutionFactor, pxMaskPkd, blendRegisterOrder); + + // convert result from pln to pkd format and store in output buffer + if constexpr (std::is_same::value) + _mm256_storeu_ps(dstPtrTemp, pDst); + else if constexpr (std::is_same::value) + _mm_storeu_si128((__m128i *)dstPtrTemp, _mm256_cvtps_ph(pDst, _MM_FROUND_TO_ZERO | _MM_FROUND_NO_EXC)); + + dstPtrTemp += 8; + pTemp[0] = pTemp[1]; + pTemp[1] = pTemp[2]; + pTemp[2] = pTemp[3]; + } + increment_row_ptrs(srcPtrTemp, kernelSize, -16); +#endif + vectorLoopCount += padLength * 3; + for (; vectorLoopCount < bufferLength; vectorLoopCount++) + { + dilate_generic_tensor(srcPtrTemp, dstPtrTemp, vectorLoopCount / 3, kernelSize, padLength, unpaddedWidth, rowKernelLoopLimit, kernelSizeInverseSquare, 3); + increment_row_ptrs(srcPtrTemp, kernelSize, 1); + dstPtrTemp++; + } + // for the first padLength rows, we need not increment the src row pointers to next rows + increment_row_ptrs(srcPtrRow, kernelSize, (!padLengthRows) ? srcDescPtr->strides.hStride : 0); + dstPtrRow += dstDescPtr->strides.hStride; + } + } + else if ((srcDescPtr->c == 3) && (srcDescPtr->layout == RpptLayout::NCHW) && (dstDescPtr->layout == RpptLayout::NHWC)) + { + /* exclude (2 * padLength) number of columns from alignedLength calculation + since padLength number of columns from the beginning and end of each row will be computed using raw c code */ + Rpp32u alignedLength = ((bufferLength - (2 * padLength)) / 16) * 16; + for(int i = 0; i < roi.xywhROI.roiHeight; i++) + { + int vectorLoopCount = 0; + bool padLengthRows = (i < padLength) ? 1: 0; + T *srcPtrTemp[3][7]; + for (int c = 0; c < 3; c++) + { + Rpp32u channelStride = c * srcDescPtr->strides.cStride; + for (int k = 0; k < 7; k++) + srcPtrTemp[c][k] = srcPtrRow[k] + channelStride; + } + T *dstPtrTemp = dstPtrRow; + + // get the number of rows needs to be loaded for the corresponding row + Rpp32s rowKernelLoopLimit = kernelSize; + get_kernel_loop_limit(i, rowKernelLoopLimit, padLength, unpaddedHeight); + + // process padLength number of columns in each row + for (int k = 0; k < padLength; k++) + { + for (int c = 0; c < 3; c++) + { + dilate_generic_tensor(srcPtrTemp[c], dstPtrTemp, k, kernelSize, padLength, unpaddedWidth, rowKernelLoopLimit, kernelSizeInverseSquare); + dstPtrTemp++; + } + } +#if __AVX2__ + // process alignedLength number of columns in each row + for (; vectorLoopCount < alignedLength; vectorLoopCount += 8) + { + __m256 pResultPln[3]; + for (int c = 0; c < 3; c++) + { + __m256 pRow[7], pTemp[2]; + rpp_load_dilate_float_7x7_host(pRow, srcPtrTemp[c], rowKernelLoopLimit); + max_rows_7x7(pRow, &pTemp[0]); + + increment_row_ptrs(srcPtrTemp[c], kernelSize, 8); + rpp_load_dilate_float_7x7_host(pRow, srcPtrTemp[c], rowKernelLoopLimit); + max_rows_7x7(pRow, &pTemp[1]); + blend_permute_max_7x7_host<1, 3, 7, 15, 31, 63>(pTemp, &pResultPln[c], pConvolutionFactor, pxMaskPln, blendRegisterOrder); + } + // convert result from pln to pkd format and store in output buffer + if constexpr (std::is_same::value) + rpp_store24_f32pln3_to_f32pkd3_avx(dstPtrTemp, pResultPln); + else if constexpr (std::is_same::value) + rpp_store24_f32pln3_to_f16pkd3_avx(dstPtrTemp, pResultPln); + + dstPtrTemp += 24; + } +#endif + vectorLoopCount += padLength; + for (; vectorLoopCount < bufferLength; vectorLoopCount++) + { + for (int c = 0; c < srcDescPtr->c; c++) + { + dilate_generic_tensor(srcPtrTemp[c], dstPtrTemp, vectorLoopCount, kernelSize, padLength, unpaddedWidth, rowKernelLoopLimit, kernelSizeInverseSquare); + increment_row_ptrs(srcPtrTemp[c], kernelSize, 1); + dstPtrTemp++; + } + } + // for the first padLength rows, we need not increment the src row pointers to next rows + increment_row_ptrs(srcPtrRow, kernelSize, (!padLengthRows) ? srcDescPtr->strides.hStride : 0); + dstPtrRow += dstDescPtr->strides.hStride; + } + } + else if ((srcDescPtr->layout == RpptLayout::NHWC) && (dstDescPtr->layout == RpptLayout::NCHW)) + { + /* exclude ((2 * padLength) * 3) number of columns from alignedLength calculation + since (padLength * 3) number of columns from the beginning and end of each row will be computed using raw c code */ + Rpp32u alignedLength = ((bufferLength - (2 * padLength) * 3) / 32) * 32; + T *dstPtrChannels[3]; + for (int i = 0; i < 3; i++) + dstPtrChannels[i] = dstPtrChannel + i * dstDescPtr->strides.cStride; + for(int i = 0; i < roi.xywhROI.roiHeight; i++) + { + int vectorLoopCount = 0; + bool padLengthRows = (i < padLength) ? 1: 0; + T *srcPtrTemp[7]; + for (int k = 0; k < 7; k++) + srcPtrTemp[k] = srcPtrRow[k]; + T *dstPtrTempChannels[3] = {dstPtrChannels[0], dstPtrChannels[1], dstPtrChannels[2]}; + + Rpp32s rowKernelLoopLimit = kernelSize; + get_kernel_loop_limit(i, rowKernelLoopLimit, padLength, unpaddedHeight); + process_left_border_columns_pkd_pln(srcPtrTemp, srcPtrRow, dstPtrTempChannels, kernelSize, padLength, unpaddedWidth, rowKernelLoopLimit, kernelSizeInverseSquare); +#if __AVX2__ + // process remaining columns in each row + for (; vectorLoopCount < alignedLength; vectorLoopCount += 12) + { + __m256 pRow[7], pTemp[5]; + rpp_load_dilate_float_7x7_host(pRow, srcPtrTemp, rowKernelLoopLimit); + max_rows_7x7(pRow, &pTemp[0]); + + increment_row_ptrs(srcPtrTemp, kernelSize, 8); + rpp_load_dilate_float_7x7_host(pRow, srcPtrTemp, rowKernelLoopLimit); + max_rows_7x7(pRow, &pTemp[1]); + + increment_row_ptrs(srcPtrTemp, kernelSize, 8); + rpp_load_dilate_float_7x7_host(pRow, srcPtrTemp, rowKernelLoopLimit); + max_rows_7x7(pRow, &pTemp[2]); + + increment_row_ptrs(srcPtrTemp, kernelSize, 8); + rpp_load_dilate_float_7x7_host(pRow, srcPtrTemp, rowKernelLoopLimit); + max_rows_7x7(pRow, &pTemp[3]); + pTemp[4] = avx_p0; + + __m256 pDst[2]; + blend_permute_max_7x7_host<7, 63, 1, 15, 127, 3>(&pTemp[0], &pDst[0], pConvolutionFactor, pxMaskPkd, blendRegisterOrder); + blend_permute_max_7x7_host<7, 63, 1, 15, 127, 3>(&pTemp[1], &pDst[1], pConvolutionFactor, pxMaskPkd, blendRegisterOrder); + + __m128 pDstPln[3]; + rpp_convert12_f32pkd3_to_f32pln3(pDst, pDstPln); + rpp_store12_float_pkd_pln(dstPtrTempChannels, pDstPln); + + increment_row_ptrs(srcPtrTemp, kernelSize, -12); + increment_row_ptrs(dstPtrTempChannels, 3, 4); + } +#endif + vectorLoopCount += padLength * 3; + + // process remaining columns in each row + for (; vectorLoopCount < bufferLength; vectorLoopCount++) + { + int channel = vectorLoopCount % 3; + dilate_generic_tensor(srcPtrTemp, dstPtrTempChannels[channel], vectorLoopCount / 3, kernelSize, padLength, unpaddedWidth, rowKernelLoopLimit, kernelSizeInverseSquare, 3); + increment_row_ptrs(srcPtrTemp, kernelSize, 1); + dstPtrTempChannels[channel]++; + } + // for the first padLength rows, we need not increment the src row pointers to next rows + increment_row_ptrs(srcPtrRow, kernelSize, (!padLengthRows) ? srcDescPtr->strides.hStride : 0); + increment_row_ptrs(dstPtrChannels, 3, dstDescPtr->strides.hStride); + } + } + } + else if (kernelSize == 9) + { + T *srcPtrRow[9], *dstPtrRow; + for (int i = 0; i < 9; i++) + srcPtrRow[i] = srcPtrChannel + i * srcDescPtr->strides.hStride; + dstPtrRow = dstPtrChannel; + + // dilate without fused output-layout toggle (NCHW -> NCHW) + if ((srcDescPtr->layout == RpptLayout::NCHW) && (dstDescPtr->layout == RpptLayout::NCHW)) + { + /* exclude (2 * padLength) number of columns from alignedLength calculation + since padLength number of columns from the beginning and end of each row will be computed using raw c code */ + Rpp32u alignedLength = ((bufferLength - (2 * padLength)) / 16) * 16; + for (int c = 0; c < srcDescPtr->c; c++) + { + srcPtrRow[0] = srcPtrChannel; + for (int k = 1; k < 9; k++) + srcPtrRow[k] = srcPtrRow[k - 1] + srcDescPtr->strides.hStride; + dstPtrRow = dstPtrChannel; + for(int i = 0; i < roi.xywhROI.roiHeight; i++) + { + int vectorLoopCount = 0; + bool padLengthRows = (i < padLength) ? 1: 0; + T *srcPtrTemp[9]; + for (int k = 0; k < 9; k++) + srcPtrTemp[k] = srcPtrRow[k]; + T *dstPtrTemp = dstPtrRow; + + // get the number of rows needs to be loaded for the corresponding row + Rpp32s rowKernelLoopLimit = kernelSize; + get_kernel_loop_limit(i, rowKernelLoopLimit, padLength, unpaddedHeight); + process_left_border_columns_pln_pln(srcPtrTemp, dstPtrTemp, kernelSize, padLength, unpaddedWidth, rowKernelLoopLimit, kernelSizeInverseSquare); + dstPtrTemp += padLength; +#if __AVX2__ + __m256 pRow[9]; + if (alignedLength) + rpp_load_dilate_float_9x9_host(pRow, srcPtrTemp, rowKernelLoopLimit); + + // process alignedLength number of columns in each row + for (; vectorLoopCount < alignedLength; vectorLoopCount += 8) + { + // compute max of loaded values from 9 rows + __m256 pTemp[2], pDst; + max_rows_9x9(pRow, &pTemp[0]); + increment_row_ptrs(srcPtrTemp, kernelSize, 8); + + rpp_load_dilate_float_9x9_host(pRow, srcPtrTemp, rowKernelLoopLimit); + max_rows_9x9(pRow, &pTemp[1]); + blend_permute_max_9x9_host<1, 3, 7, 15, 31, 63, 127>(pTemp, &pDst, pConvolutionFactor, pxMaskPln, blendRegisterOrder); + + if constexpr (std::is_same::value) + _mm256_storeu_ps(dstPtrTemp, pDst); + else if constexpr (std::is_same::value) + _mm_storeu_si128((__m128i *)dstPtrTemp, _mm256_cvtps_ph(pDst, _MM_FROUND_TO_ZERO | _MM_FROUND_NO_EXC)); + + dstPtrTemp += 8; + } +#endif + vectorLoopCount += padLength; + for (; vectorLoopCount < bufferLength; vectorLoopCount++) + { + dilate_generic_tensor(srcPtrTemp, dstPtrTemp, vectorLoopCount, kernelSize, padLength, unpaddedWidth, rowKernelLoopLimit, kernelSizeInverseSquare); + increment_row_ptrs(srcPtrTemp, kernelSize, 1); + dstPtrTemp++; + } + // for the first padLength rows, we need not increment the src row pointers to next rows + increment_row_ptrs(srcPtrRow, kernelSize, (!padLengthRows) ? srcDescPtr->strides.hStride : 0); + dstPtrRow += dstDescPtr->strides.hStride; + } + srcPtrChannel += srcDescPtr->strides.cStride; + dstPtrChannel += dstDescPtr->strides.cStride; + } + } + else if ((srcDescPtr->layout == RpptLayout::NHWC) && (dstDescPtr->layout == RpptLayout::NHWC)) + { + /* exclude ((2 * padLength) * 3) number of columns from alignedLength calculation + since (padLength * 3) number of columns from the beginning and end of each row will be computed using raw c code */ + Rpp32u alignedLength = ((bufferLength - (2 * padLength) * 3) / 32) * 32; + for(int i = 0; i < roi.xywhROI.roiHeight; i++) + { + int vectorLoopCount = 0; + bool padLengthRows = (i < padLength) ? 1: 0; + T *srcPtrTemp[9]; + for (int k = 0; k < 9; k++) + srcPtrTemp[k] = srcPtrRow[k]; + T *dstPtrTemp = dstPtrRow; + + Rpp32s rowKernelLoopLimit = kernelSize; + get_kernel_loop_limit(i, rowKernelLoopLimit, padLength, unpaddedHeight); + process_left_border_columns_pkd_pkd(srcPtrTemp, srcPtrRow, dstPtrTemp, kernelSize, padLength, unpaddedWidth, rowKernelLoopLimit, kernelSizeInverseSquare); + dstPtrTemp += padLength * 3; +#if __AVX2__ + __m256 pRow[9], pTemp[4]; + if (alignedLength) + { + rpp_load_dilate_float_9x9_host(pRow, srcPtrTemp, rowKernelLoopLimit); + max_rows_9x9(pRow, &pTemp[0]); + + increment_row_ptrs(srcPtrTemp, kernelSize, 8); + rpp_load_dilate_float_9x9_host(pRow, srcPtrTemp, rowKernelLoopLimit); + max_rows_9x9(pRow, &pTemp[1]); + + increment_row_ptrs(srcPtrTemp, kernelSize, 8); + rpp_load_dilate_float_9x9_host(pRow, srcPtrTemp, rowKernelLoopLimit); + max_rows_9x9(pRow, &pTemp[2]); + } + + // process remaining columns in each row + for (; vectorLoopCount < alignedLength; vectorLoopCount += 8) + { + // compute max of loaded values from 9 rows + increment_row_ptrs(srcPtrTemp, kernelSize, 8); + rpp_load_dilate_float_9x9_host(pRow, srcPtrTemp, rowKernelLoopLimit); + max_rows_9x9(pRow, &pTemp[3]); + + __m256 pDst; + blend_permute_max_9x9_host<7, 63, 1, 15, 127, 3, 31>(pTemp, &pDst, pConvolutionFactor, pxMaskPkd, blendRegisterOrder); + if constexpr (std::is_same::value) + _mm256_storeu_ps(dstPtrTemp, pDst); + else if constexpr (std::is_same::value) + _mm_storeu_si128((__m128i *)dstPtrTemp, _mm256_cvtps_ph(pDst, _MM_FROUND_TO_ZERO | _MM_FROUND_NO_EXC)); + + dstPtrTemp += 8; + pTemp[0] = pTemp[1]; + pTemp[1] = pTemp[2]; + pTemp[2] = pTemp[3]; + } + increment_row_ptrs(srcPtrTemp, kernelSize, -16); +#endif + vectorLoopCount += padLength * 3; + for (; vectorLoopCount < bufferLength; vectorLoopCount++) + { + dilate_generic_tensor(srcPtrTemp, dstPtrTemp, vectorLoopCount / 3, kernelSize, padLength, unpaddedWidth, rowKernelLoopLimit, kernelSizeInverseSquare, 3); + increment_row_ptrs(srcPtrTemp, kernelSize, 1); + dstPtrTemp++; + } + // for the first padLength rows, we need not increment the src row pointers to next rows + increment_row_ptrs(srcPtrRow, kernelSize, (!padLengthRows) ? srcDescPtr->strides.hStride : 0); + dstPtrRow += dstDescPtr->strides.hStride; + } + } + // dilate with fused output-layout toggle (NCHW -> NHWC) + else if ((srcDescPtr->layout == RpptLayout::NCHW) && (dstDescPtr->layout == RpptLayout::NHWC)) + { + /* exclude (2 * padLength) number of columns from alignedLength calculation + since padLength number of columns from the beginning and end of each row will be computed using raw c code */ + Rpp32u alignedLength = ((bufferLength - (2 * padLength)) / 16) * 16; + for(int i = 0; i < roi.xywhROI.roiHeight; i++) + { + int vectorLoopCount = 0; + bool padLengthRows = (i < padLength) ? 1: 0; + T *srcPtrTemp[3][9]; + for (int c = 0; c < 3; c++) + { + Rpp32u channelStride = c * srcDescPtr->strides.cStride; + for (int k = 0; k < 9; k++) + srcPtrTemp[c][k] = srcPtrRow[k] + channelStride; + } + T *dstPtrTemp = dstPtrRow; + + // get the number of rows needs to be loaded for the corresponding row + Rpp32s rowKernelLoopLimit = kernelSize; + get_kernel_loop_limit(i, rowKernelLoopLimit, padLength, unpaddedHeight); + for (int k = 0; k < padLength; k++) + { + for (int c = 0; c < 3; c++) + { + dilate_generic_tensor(srcPtrTemp[c], dstPtrTemp, k, kernelSize, padLength, unpaddedWidth, rowKernelLoopLimit, kernelSizeInverseSquare); + dstPtrTemp++; + } + } +#if __AVX2__ + // process alignedLength number of columns in each row + for (; vectorLoopCount < alignedLength; vectorLoopCount += 8) + { + __m256 pResultPln[3]; + for (int c = 0; c < 3; c++) + { + // compute max of loaded values from 9 rows + __m256 pRow[9], pTemp[2]; + rpp_load_dilate_float_9x9_host(pRow, srcPtrTemp[c], rowKernelLoopLimit); + max_rows_9x9(pRow, &pTemp[0]); + + increment_row_ptrs(srcPtrTemp[c], kernelSize, 8); + rpp_load_dilate_float_9x9_host(pRow, srcPtrTemp[c], rowKernelLoopLimit); + max_rows_9x9(pRow, &pTemp[1]); + + blend_permute_max_9x9_host<1, 3, 7, 15, 31, 63, 127>(pTemp, &pResultPln[c], pConvolutionFactor, pxMaskPln, blendRegisterOrder); + } + + if constexpr (std::is_same::value) + rpp_store24_f32pln3_to_f32pkd3_avx(dstPtrTemp, pResultPln); + else if constexpr (std::is_same::value) + rpp_store24_f32pln3_to_f16pkd3_avx(dstPtrTemp, pResultPln); + dstPtrTemp += 24; + } +#endif + vectorLoopCount += padLength; + for (; vectorLoopCount < bufferLength; vectorLoopCount++) + { + for (int c = 0; c < srcDescPtr->c; c++) + { + dilate_generic_tensor(srcPtrTemp[c], dstPtrTemp, vectorLoopCount, kernelSize, padLength, unpaddedWidth, rowKernelLoopLimit, kernelSizeInverseSquare); + increment_row_ptrs(srcPtrTemp[c], kernelSize, 1); + dstPtrTemp++; + } + } + // for the first padLength rows, we need not increment the src row pointers to next rows + increment_row_ptrs(srcPtrRow, kernelSize, (!padLengthRows) ? srcDescPtr->strides.hStride : 0); + dstPtrRow += dstDescPtr->strides.hStride; + } + } + else if ((srcDescPtr->layout == RpptLayout::NHWC) && (dstDescPtr->layout == RpptLayout::NCHW)) + { + /* exclude ((2 * padLength) * 3) number of columns from alignedLength calculation + since (padLength * 3) number of columns from the beginning and end of each row will be computed using raw c code */ + Rpp32u alignedLength = ((bufferLength - (2 * padLength) * 3) / 40) * 40; + T *dstPtrChannels[3]; + for (int i = 0; i < 3; i++) + dstPtrChannels[i] = dstPtrChannel + i * dstDescPtr->strides.cStride; + for(int i = 0; i < roi.xywhROI.roiHeight; i++) + { + int vectorLoopCount = 0; + bool padLengthRows = (i < padLength) ? 1: 0; + T *srcPtrTemp[9]; + for (int k = 0; k < 9; k++) + srcPtrTemp[k] = srcPtrRow[k]; + T *dstPtrTempChannels[3] = {dstPtrChannels[0], dstPtrChannels[1], dstPtrChannels[2]}; + + Rpp32s rowKernelLoopLimit = kernelSize; + get_kernel_loop_limit(i, rowKernelLoopLimit, padLength, unpaddedHeight); + process_left_border_columns_pkd_pln(srcPtrTemp, srcPtrRow, dstPtrTempChannels, kernelSize, padLength, unpaddedWidth, rowKernelLoopLimit, kernelSizeInverseSquare); +#if __AVX2__ + // process remaining columns in each row + for (; vectorLoopCount < alignedLength; vectorLoopCount += 12) + { + __m256 pRow[9], pTemp[5]; + rpp_load_dilate_float_9x9_host(pRow, srcPtrTemp, rowKernelLoopLimit); + max_rows_9x9(pRow, &pTemp[0]); + + increment_row_ptrs(srcPtrTemp, kernelSize, 8); + rpp_load_dilate_float_9x9_host(pRow, srcPtrTemp, rowKernelLoopLimit); + max_rows_9x9(pRow, &pTemp[1]); + + increment_row_ptrs(srcPtrTemp, kernelSize, 8); + rpp_load_dilate_float_9x9_host(pRow, srcPtrTemp, rowKernelLoopLimit); + max_rows_9x9(pRow, &pTemp[2]); + + increment_row_ptrs(srcPtrTemp, kernelSize, 8); + rpp_load_dilate_float_9x9_host(pRow, srcPtrTemp, rowKernelLoopLimit); + max_rows_9x9(pRow, &pTemp[3]); + + increment_row_ptrs(srcPtrTemp, kernelSize, 8); + rpp_load_dilate_float_9x9_host(pRow, srcPtrTemp, rowKernelLoopLimit); + max_rows_9x9(pRow, &pTemp[4]); + + __m256 pDst[2]; + blend_permute_max_9x9_host<7, 63, 1, 15, 127, 3, 31>(&pTemp[0], &pDst[0], pConvolutionFactor, pxMaskPkd, blendRegisterOrder); + blend_permute_max_9x9_host<7, 63, 1, 15, 127, 3, 31>(&pTemp[1], &pDst[1], pConvolutionFactor, pxMaskPkd, blendRegisterOrder); + + __m128 pDstPln[3]; + rpp_convert12_f32pkd3_to_f32pln3(pDst, pDstPln); + rpp_store12_float_pkd_pln(dstPtrTempChannels, pDstPln); + + increment_row_ptrs(srcPtrTemp, kernelSize, -20); + increment_row_ptrs(dstPtrTempChannels, 3, 4); + } +#endif + vectorLoopCount += padLength * 3; + + // process remaining columns in each row + for (; vectorLoopCount < bufferLength; vectorLoopCount++) + { + int channel = vectorLoopCount % 3; + dilate_generic_tensor(srcPtrTemp, dstPtrTempChannels[channel], vectorLoopCount / 3, kernelSize, padLength, unpaddedWidth, rowKernelLoopLimit, kernelSizeInverseSquare, 3); + increment_row_ptrs(srcPtrTemp, kernelSize, 1); + dstPtrTempChannels[channel]++; + } + // for the first padLength rows, we need not increment the src row pointers to next rows + increment_row_ptrs(srcPtrRow, kernelSize, (!padLengthRows) ? srcDescPtr->strides.hStride : 0); + increment_row_ptrs(dstPtrChannels, 3, dstDescPtr->strides.hStride); + } + } + } + } + + return RPP_SUCCESS; +} + +template +RppStatus dilate_generic_host_tensor(T *srcPtr, + RpptDescPtr srcDescPtr, + T *dstPtr, + RpptDescPtr dstDescPtr, + Rpp32u kernelSize, + RpptROIPtr roiTensorPtrSrc, + RpptRoiType roiType, + RppLayoutParams layoutParams, + rpp::Handle& handle) +{ + RpptROI roiDefault = {0, 0, (Rpp32s)srcDescPtr->w, (Rpp32s)srcDescPtr->h}; + Rpp32u numThreads = handle.GetNumThreads(); + + omp_set_dynamic(0); +#pragma omp parallel for num_threads(numThreads) + for(int batchCount = 0; batchCount < dstDescPtr->n; batchCount++) + { + RpptROI roi; + RpptROIPtr roiPtrInput = &roiTensorPtrSrc[batchCount]; + compute_roi_validation_host(roiPtrInput, &roi, &roiDefault, roiType); + + T *srcPtrImage, *dstPtrImage; + srcPtrImage = srcPtr + batchCount * srcDescPtr->strides.nStride; + dstPtrImage = dstPtr + batchCount * dstDescPtr->strides.nStride; + + Rpp32u padLength = kernelSize / 2; + Rpp32u bufferLength = roi.xywhROI.roiWidth * layoutParams.bufferMultiplier; + Rpp32f kernelSizeInverseSquare = 1.0 / (kernelSize * kernelSize); + Rpp32u unpaddedHeight = roi.xywhROI.roiHeight - padLength; + Rpp32u unpaddedWidth = roi.xywhROI.roiWidth - padLength; + + T *srcPtrChannel, *dstPtrChannel; + srcPtrChannel = srcPtrImage + (roi.xywhROI.xy.y * srcDescPtr->strides.hStride) + (roi.xywhROI.xy.x * layoutParams.bufferMultiplier); + dstPtrChannel = dstPtrImage; + + T *srcPtrRow[kernelSize], *dstPtrRow; + for (int k = 0; k < kernelSize; k++) + srcPtrRow[k] = srcPtrChannel + k * srcDescPtr->strides.hStride; + dstPtrRow = dstPtrChannel; + if ((srcDescPtr->layout == RpptLayout::NCHW) && (dstDescPtr->layout == RpptLayout::NCHW)) + { + for (int c = 0; c < srcDescPtr->c; c++) + { + srcPtrRow[0] = srcPtrChannel; + for (int k = 1; k < kernelSize; k++) + srcPtrRow[k] = srcPtrRow[k - 1] + srcDescPtr->strides.hStride; + dstPtrRow = dstPtrChannel; + for(int i = 0; i < roi.xywhROI.roiHeight; i++) + { + int vectorLoopCount = 0; + bool padLengthRows = (i < padLength) ? 1: 0; + T *srcPtrTemp[kernelSize]; + for (int k = 0; k < kernelSize; k++) + srcPtrTemp[k] = srcPtrRow[k]; + T *dstPtrTemp = dstPtrRow; + + Rpp32s rowKernelLoopLimit = kernelSize; + get_kernel_loop_limit(i, rowKernelLoopLimit, padLength, unpaddedHeight); + process_left_border_columns_pln_pln(srcPtrTemp, dstPtrTemp, kernelSize, padLength, unpaddedWidth, rowKernelLoopLimit, kernelSizeInverseSquare); + dstPtrTemp += padLength; + vectorLoopCount += padLength; + + // process remaining columns in each row + for (; vectorLoopCount < bufferLength; vectorLoopCount++) + { + dilate_generic_tensor(srcPtrTemp, dstPtrTemp, vectorLoopCount, kernelSize, padLength, unpaddedWidth, rowKernelLoopLimit, kernelSizeInverseSquare); + increment_row_ptrs(srcPtrTemp, kernelSize, 1); + dstPtrTemp++; + } + // for the first padLength rows, we need not increment the src row pointers to next rows + increment_row_ptrs(srcPtrRow, kernelSize, (!padLengthRows) ? srcDescPtr->strides.hStride : 0); + dstPtrRow += dstDescPtr->strides.hStride; + } + srcPtrChannel += srcDescPtr->strides.cStride; + dstPtrChannel += dstDescPtr->strides.cStride; + } + } + else if ((srcDescPtr->c == 3) && (srcDescPtr->layout == RpptLayout::NHWC) && (dstDescPtr->layout == RpptLayout::NHWC)) + { + for(int i = 0; i < roi.xywhROI.roiHeight; i++) + { + int vectorLoopCount = 0; + bool padLengthRows = (i < padLength) ? 1: 0; + T *srcPtrTemp[kernelSize]; + for (int k = 0; k < kernelSize; k++) + srcPtrTemp[k] = srcPtrRow[k]; + T *dstPtrTemp = dstPtrRow; + + Rpp32s rowKernelLoopLimit = kernelSize; + get_kernel_loop_limit(i, rowKernelLoopLimit, padLength, unpaddedHeight); + process_left_border_columns_pkd_pkd(srcPtrTemp, srcPtrRow, dstPtrTemp, kernelSize, padLength, unpaddedWidth, rowKernelLoopLimit, kernelSizeInverseSquare); + dstPtrTemp += padLength * 3; + vectorLoopCount += padLength * 3; + + // process remaining columns in each row + for (; vectorLoopCount < bufferLength; vectorLoopCount++) + { + dilate_generic_tensor(srcPtrTemp, dstPtrTemp, vectorLoopCount / 3, kernelSize, padLength, unpaddedWidth, rowKernelLoopLimit, kernelSizeInverseSquare, 3); + increment_row_ptrs(srcPtrTemp, kernelSize, 1); + dstPtrTemp++; + } + // for the first padLength rows, we need not increment the src row pointers to next rows + increment_row_ptrs(srcPtrRow, kernelSize, (!padLengthRows) ? srcDescPtr->strides.hStride : 0); + dstPtrRow += dstDescPtr->strides.hStride; + } + } + else if ((srcDescPtr->layout == RpptLayout::NCHW) && (dstDescPtr->layout == RpptLayout::NHWC)) + { + for(int i = 0; i < roi.xywhROI.roiHeight; i++) + { + int vectorLoopCount = 0; + bool padLengthRows = (i < padLength) ? 1: 0; + T *srcPtrTemp[3][kernelSize]; + for (int c = 0; c < 3; c++) + { + Rpp32u channelStride = c * srcDescPtr->strides.cStride; + for (int k = 0; k < kernelSize; k++) + srcPtrTemp[c][k] = srcPtrRow[k] + channelStride; + } + T *dstPtrTemp = dstPtrRow; + + Rpp32s rowKernelLoopLimit = kernelSize; + get_kernel_loop_limit(i, rowKernelLoopLimit, padLength, unpaddedHeight); + + // process padLength number of columns in each row + for (int k = 0; k < padLength; k++) + { + for (int c = 0; c < 3; c++) + { + dilate_generic_tensor(srcPtrTemp[c], dstPtrTemp, k, kernelSize, padLength, unpaddedWidth, rowKernelLoopLimit, kernelSizeInverseSquare); + dstPtrTemp++; + } + } + vectorLoopCount += padLength; + + // process remaining columns in each row + for (; vectorLoopCount < bufferLength; vectorLoopCount++) + { + for (int c = 0; c < srcDescPtr->c; c++) + { + dilate_generic_tensor(srcPtrTemp[c], dstPtrTemp, vectorLoopCount, kernelSize, padLength, unpaddedWidth, rowKernelLoopLimit, kernelSizeInverseSquare); + increment_row_ptrs(srcPtrTemp[c], kernelSize, 1); + dstPtrTemp++; + } + } + // for the first padLength rows, we need not increment the src row pointers to next rows + increment_row_ptrs(srcPtrRow, kernelSize, (!padLengthRows) ? srcDescPtr->strides.hStride : 0); + dstPtrRow += dstDescPtr->strides.hStride; + } + } + else if ((srcDescPtr->layout == RpptLayout::NHWC) && (dstDescPtr->layout == RpptLayout::NCHW)) + { + T *dstPtrChannels[3]; + for (int c = 0; c < 3; c++) + dstPtrChannels[c] = dstPtrChannel + c * dstDescPtr->strides.cStride; + for(int i = 0; i < roi.xywhROI.roiHeight; i++) + { + int vectorLoopCount = 0; + bool padLengthRows = (i < padLength) ? 1: 0; + T *srcPtrTemp[kernelSize]; + for (int k = 0; k < kernelSize; k++) + srcPtrTemp[k] = srcPtrRow[k]; + T *dstPtrTempChannels[3] = {dstPtrChannels[0], dstPtrChannels[1], dstPtrChannels[2]}; + + Rpp32s rowKernelLoopLimit = kernelSize; + get_kernel_loop_limit(i, rowKernelLoopLimit, padLength, unpaddedHeight); + process_left_border_columns_pkd_pln(srcPtrTemp, srcPtrRow, dstPtrTempChannels, kernelSize, padLength, unpaddedWidth, rowKernelLoopLimit, kernelSizeInverseSquare); + vectorLoopCount += padLength * 3; + + // process remaining columns in each row + for (; vectorLoopCount < bufferLength; vectorLoopCount++) + { + int channel = vectorLoopCount % 3; + dilate_generic_tensor(srcPtrTemp, dstPtrTempChannels[channel], vectorLoopCount / 3, kernelSize, padLength, unpaddedWidth, rowKernelLoopLimit, kernelSizeInverseSquare, 3); + increment_row_ptrs(srcPtrTemp, kernelSize, 1); + dstPtrTempChannels[channel]++; + } + // for the first padLength rows, we need not increment the src row pointers to next rows + increment_row_ptrs(srcPtrRow, kernelSize, (!padLengthRows) ? srcDescPtr->strides.hStride : 0); + increment_row_ptrs(dstPtrChannels, 3, dstDescPtr->strides.hStride); + } + } + } + return RPP_SUCCESS; +} diff --git a/src/modules/rppt_tensor_morphological_operations.cpp b/src/modules/rppt_tensor_morphological_operations.cpp index 218eeb7d5..cac8d3830 100644 --- a/src/modules/rppt_tensor_morphological_operations.cpp +++ b/src/modules/rppt_tensor_morphological_operations.cpp @@ -25,12 +25,78 @@ SOFTWARE. #include "rppdefs.h" #include "rppi_validate.hpp" #include "rppt_tensor_morphological_operations.h" +#include "cpu/host_tensor_morphological_operations.hpp" #ifdef HIP_COMPILE #include #include "hip/hip_tensor_morphological_operations.hpp" #endif // HIP_COMPILE +/******************** dilate ********************/ + +RppStatus rppt_dilate_host(RppPtr_t srcPtr, + RpptDescPtr srcDescPtr, + RppPtr_t dstPtr, + RpptDescPtr dstDescPtr, + Rpp32u kernelSize, + RpptROIPtr roiTensorPtrSrc, + RpptRoiType roiType, + rppHandle_t rppHandle) +{ + RppLayoutParams layoutParams = get_layout_params(srcDescPtr->layout, srcDescPtr->c); + + if ((srcDescPtr->dataType == RpptDataType::U8) && (dstDescPtr->dataType == RpptDataType::U8)) + { + dilate_char_host_tensor(static_cast(srcPtr) + srcDescPtr->offsetInBytes, + srcDescPtr, + static_cast(dstPtr) + dstDescPtr->offsetInBytes, + dstDescPtr, + kernelSize, + roiTensorPtrSrc, + roiType, + layoutParams, + rpp::deref(rppHandle)); + } + else if ((srcDescPtr->dataType == RpptDataType::F16) && (dstDescPtr->dataType == RpptDataType::F16)) + { + dilate_float_host_tensor(reinterpret_cast(static_cast(srcPtr) + srcDescPtr->offsetInBytes), + srcDescPtr, + reinterpret_cast(static_cast(dstPtr) + dstDescPtr->offsetInBytes), + dstDescPtr, + kernelSize, + roiTensorPtrSrc, + roiType, + layoutParams, + rpp::deref(rppHandle)); + } + else if ((srcDescPtr->dataType == RpptDataType::F32) && (dstDescPtr->dataType == RpptDataType::F32)) + { + dilate_float_host_tensor(reinterpret_cast(static_cast(srcPtr) + srcDescPtr->offsetInBytes), + srcDescPtr, + reinterpret_cast(static_cast(dstPtr) + dstDescPtr->offsetInBytes), + dstDescPtr, + kernelSize, + roiTensorPtrSrc, + roiType, + layoutParams, + rpp::deref(rppHandle)); + } + else if ((srcDescPtr->dataType == RpptDataType::I8) && (dstDescPtr->dataType == RpptDataType::I8)) + { + dilate_char_host_tensor(static_cast(srcPtr) + srcDescPtr->offsetInBytes, + srcDescPtr, + static_cast(dstPtr) + dstDescPtr->offsetInBytes, + dstDescPtr, + kernelSize, + roiTensorPtrSrc, + roiType, + layoutParams, + rpp::deref(rppHandle)); + } + + return RPP_SUCCESS; +} + /********************************************************************************************************************/ /*********************************************** RPP_GPU_SUPPORT = ON ***********************************************/ /********************************************************************************************************************/ diff --git a/utilities/test_suite/HIP/Tensor_hip.cpp b/utilities/test_suite/HIP/Tensor_hip.cpp index bfdc008f4..bbc5009ad 100644 --- a/utilities/test_suite/HIP/Tensor_hip.cpp +++ b/utilities/test_suite/HIP/Tensor_hip.cpp @@ -192,7 +192,7 @@ int main(int argc, char **argv) { char additionalParam_char[2]; std::sprintf(additionalParam_char, "%u", additionalParam); - func += "_kSize"; + func += "_kernelSize"; func += additionalParam_char; } else if (interpolationTypeCase) @@ -277,8 +277,12 @@ int main(int argc, char **argv) exit(0); } + Rpp32s additionalStride = 0; + if (kernelSizeCase) + additionalStride = additionalParam / 2; + // Set numDims, offset, n/c/h/w values, strides for src/dst - set_descriptor_dims_and_strides(srcDescPtr, batchSize, maxHeight, maxWidth, inputChannels, srcOffsetInBytes); + set_descriptor_dims_and_strides(srcDescPtr, batchSize, maxHeight, maxWidth, inputChannels, srcOffsetInBytes, additionalStride); set_descriptor_dims_and_strides(dstDescPtr, batchSize, maxHeight, maxWidth, outputChannels, dstOffsetInBytes); // Factors to convert U8 data to F32, F16 data to 0-1 range and reconvert them back to 0 -255 range @@ -1087,6 +1091,19 @@ int main(int argc, char **argv) break; } + case 41: + { + testCaseName = "dilate"; + Rpp32u kernelSize = additionalParam; + + startWallTime = omp_get_wtime(); + if (inputBitDepth == 0 || inputBitDepth == 1 || inputBitDepth == 2 || inputBitDepth == 5) + rppt_dilate_gpu(d_input, srcDescPtr, d_output, dstDescPtr, kernelSize, roiTensorPtrSrc, roiTypeSrc, handle); + else + missingFuncFlag = 1; + + break; + } case 45: { testCaseName = "color_temperature"; @@ -1589,12 +1606,12 @@ int main(int argc, char **argv) 3.source and destination layout are the same 4.augmentation case does not generate random output*/ if(qaFlag && inputBitDepth == 0 && ((srcDescPtr->layout == dstDescPtr->layout) || pln1OutTypeCase) && !(randomOutputCase) && !(nonQACase)) - compare_output(outputu8, testCaseName, srcDescPtr, dstDescPtr, dstImgSizes, batchSize, interpolationTypeName, noiseTypeName, testCase, dst, scriptPath); + compare_output(outputu8, testCaseName, srcDescPtr, dstDescPtr, dstImgSizes, batchSize, interpolationTypeName, noiseTypeName, additionalParam, testCase, dst, scriptPath); // Calculate exact dstROI in XYWH format for OpenCV dump if (roiTypeSrc == RpptRoiType::LTRB) convert_roi(roiTensorPtrDst, RpptRoiType::XYWH, dstDescPtr->n); - + // Check if the ROI values for each input is within the bounds of the max buffer allocated RpptROI roiDefault; RpptROIPtr roiPtrDefault = &roiDefault; diff --git a/utilities/test_suite/HIP/runTests.py b/utilities/test_suite/HIP/runTests.py index 8857e6ac5..0f6f521cc 100644 --- a/utilities/test_suite/HIP/runTests.py +++ b/utilities/test_suite/HIP/runTests.py @@ -65,15 +65,15 @@ def run_unit_test(srcPath1, srcPath2, dstPathTemp, case, numRuns, testType, layo if case == "40" or case == "41" or case == "49" or case == "54": for kernelSize in range(3, 10, 2): - print("\n./Tensor_hip " + srcPath1 + " " + srcPath2 + " " + dstPath + " " + str(bitDepth) + " " + str(outputFormatToggle) + " " + str(case) + " " + str(kernelSize)) - result = subprocess.Popen([buildFolderPath + "/build/Tensor_hip", srcPath1, srcPath2, dstPathTemp, str(bitDepth), str(outputFormatToggle), str(case), str(kernelSize), str(numRuns), str(testType), str(layout), "0", str(qaMode), str(decoderType), str(batchSize)] + roiList + [scriptPath], stdout=subprocess.PIPE) # nosec + print("./Tensor_hip " + srcPath1 + " " + srcPath2 + " " + dstPath + " " + str(bitDepth) + " " + str(outputFormatToggle) + " " + str(case) + " " + str(kernelSize)) + result = subprocess.Popen([buildFolderPath + "/build/Tensor_hip", srcPath1, srcPath2, dstPathTemp, str(bitDepth), str(outputFormatToggle), str(case), str(kernelSize), str(numRuns), str(testType), str(layout), "0", str(qaMode), str(decoderType), str(batchSize)] + roiList + [scriptPath], stdout=subprocess.PIPE, stderr=subprocess.PIPE) # nosec stdout_data, stderr_data = result.communicate() print(stdout_data.decode()) elif case == "8": # Run all variants of noise type functions with additional argument of noiseType = gausssianNoise / shotNoise / saltandpepperNoise for noiseType in range(3): - print("\n./Tensor_hip " + srcPath1 + " " + srcPath2 + " " + dstPathTemp + " " + str(bitDepth) + " " + str(outputFormatToggle) + " " + str(case) + " " + str(noiseType)) - result = subprocess.Popen([buildFolderPath + "/build/Tensor_hip", srcPath1, srcPath2, dstPathTemp, str(bitDepth), str(outputFormatToggle), str(case), str(noiseType), str(numRuns), str(testType), str(layout), "0", str(qaMode), str(decoderType), str(batchSize)] + roiList + [scriptPath], stdout=subprocess.PIPE) # nosec + print("./Tensor_hip " + srcPath1 + " " + srcPath2 + " " + dstPathTemp + " " + str(bitDepth) + " " + str(outputFormatToggle) + " " + str(case) + " " + str(noiseType)) + result = subprocess.Popen([buildFolderPath + "/build/Tensor_hip", srcPath1, srcPath2, dstPathTemp, str(bitDepth), str(outputFormatToggle), str(case), str(noiseType), str(numRuns), str(testType), str(layout), "0", str(qaMode), str(decoderType), str(batchSize)] + roiList + [scriptPath], stdout=subprocess.PIPE, stderr=subprocess.PIPE) # nosec stdout_data, stderr_data = result.communicate() print(stdout_data.decode()) elif case == "21" or case == "23" or case == "24" or case == "79": @@ -82,13 +82,13 @@ def run_unit_test(srcPath1, srcPath2, dstPathTemp, case, numRuns, testType, layo if case =='79': interpolationRange = 2 for interpolationType in range(interpolationRange): - print("\n./Tensor_hip " + srcPath1 + " " + srcPath2 + " " + dstPathTemp + " " + str(bitDepth) + " " + str(outputFormatToggle) + " " + str(case) + " " + str(interpolationType)) - result = subprocess.Popen([buildFolderPath + "/build/Tensor_hip", srcPath1, srcPath2, dstPathTemp, str(bitDepth), str(outputFormatToggle), str(case), str(interpolationType), str(numRuns), str(testType), str(layout), "0", str(qaMode), str(decoderType), str(batchSize)] + roiList + [scriptPath], stdout=subprocess.PIPE) # nosec + print("./Tensor_hip " + srcPath1 + " " + srcPath2 + " " + dstPathTemp + " " + str(bitDepth) + " " + str(outputFormatToggle) + " " + str(case) + " " + str(interpolationType)) + result = subprocess.Popen([buildFolderPath + "/build/Tensor_hip", srcPath1, srcPath2, dstPathTemp, str(bitDepth), str(outputFormatToggle), str(case), str(interpolationType), str(numRuns), str(testType), str(layout), "0", str(qaMode), str(decoderType), str(batchSize)] + roiList + [scriptPath], stdout=subprocess.PIPE,stderr=subprocess.PIPE) # nosec stdout_data, stderr_data = result.communicate() print(stdout_data.decode()) else: print("./Tensor_hip " + srcPath1 + " " + srcPath2 + " " + dstPathTemp + " " + str(bitDepth) + " " + str(outputFormatToggle) + " " + str(case) + " 0 " + str(numRuns) + " " + str(testType) + " " + str(layout)) - result = subprocess.Popen([buildFolderPath + "/build/Tensor_hip", srcPath1, srcPath2, dstPathTemp, str(bitDepth), str(outputFormatToggle), str(case), "0", str(numRuns), str(testType), str(layout), "0", str(qaMode), str(decoderType), str(batchSize)] + roiList + [scriptPath], stdout=subprocess.PIPE) # nosec + result = subprocess.Popen([buildFolderPath + "/build/Tensor_hip", srcPath1, srcPath2, dstPathTemp, str(bitDepth), str(outputFormatToggle), str(case), "0", str(numRuns), str(testType), str(layout), "0", str(qaMode), str(decoderType), str(batchSize)] + roiList + [scriptPath], stdout=subprocess.PIPE, stderr=subprocess.PIPE) # nosec stdout_data, stderr_data = result.communicate() print(stdout_data.decode()) @@ -97,7 +97,7 @@ def run_unit_test(srcPath1, srcPath2, dstPathTemp, case, numRuns, testType, layo def run_performance_test_cmd(loggingFolder, logFileLayout, srcPath1, srcPath2, dstPath, bitDepth, outputFormatToggle, case, additionalParam, numRuns, testType, layout, qaMode, decoderType, batchSize, roiList): with open(loggingFolder + "/Tensor_hip_" + logFileLayout + "_raw_performance_log.txt", "a") as logFile: print("./Tensor_hip " + srcPath1 + " " + srcPath2 + " " + dstPath + " " + str(bitDepth) + " " + str(outputFormatToggle) + " " + str(case) + " " + str(additionalParam)) - process = subprocess.Popen([buildFolderPath + "/build/Tensor_hip", srcPath1, srcPath2, dstPath, str(bitDepth), str(outputFormatToggle), str(case), str(additionalParam), str(numRuns), str(testType), str(layout), "0", str(qaMode), str(decoderType), str(batchSize)] + roiList + [scriptPath], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) # nosec + process = subprocess.Popen([buildFolderPath + "/build/Tensor_hip", srcPath1, srcPath2, dstPath, str(bitDepth), str(outputFormatToggle), str(case), str(additionalParam), str(numRuns), str(testType), str(layout), "0", str(qaMode), str(decoderType), str(batchSize)] + roiList + [scriptPath], stdout=subprocess.PIPE, stderr=subprocess.PIPE) # nosec read_from_subprocess_and_write_to_log(process, logFile) def run_performance_test(loggingFolder, logFileLayout, srcPath1, srcPath2, dstPath, case, numRuns, testType, layout, qaMode, decoderType, batchSize, roiList): @@ -133,7 +133,7 @@ def run_performance_test_with_profiler(loggingFolder, logFileLayout, srcPath1, s os.makedirs(dstPath + "/Tensor_" + layoutName + "/case_" + str(case)) with open(loggingFolder + "/Tensor_hip_" + logFileLayout + "_raw_performance_log.txt", "a") as logFile: logFile.write("rocprof --basenames on --timestamp on --stats -o " + dstPath + "/Tensor_" + layoutName + "/case_" + str(case) + "/output_case" + str(case) + "_bitDepth" + str(bitDepth) + "_oft" + addtionalParamString + ".csv ./Tensor_hip " + srcPath1 + " " + srcPath2 + " " + str(bitDepth) + " " + str(outputFormatToggle) + " " + str(case) + " " + str(additionalParam) + " 0\n") - process = subprocess.Popen(['rocprof', '--basenames', 'on', '--timestamp', 'on', '--stats', '-o', dstPath + "/Tensor_" + layoutName + "/case_" + str(case) + "/output_case" + str(case) + "_bitDepth" + str(bitDepth) + "_oft" + addtionalParamString + ".csv", buildFolderPath + "/build/Tensor_hip", srcPath1, srcPath2, dstPath, str(bitDepth), str(outputFormatToggle), str(case), str(additionalParam), str(numRuns), str(testType), str(layout), '0', str(qaMode), str(decoderType), str(batchSize)] + roiList + [scriptPath], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) # nosec + process = subprocess.Popen(['rocprof', '--basenames', 'on', '--timestamp', 'on', '--stats', '-o', dstPath + "/Tensor_" + layoutName + "/case_" + str(case) + "/output_case" + str(case) + "_bitDepth" + str(bitDepth) + "_oft" + addtionalParamString + ".csv", buildFolderPath + "/build/Tensor_hip", srcPath1, srcPath2, dstPath, str(bitDepth), str(outputFormatToggle), str(case), str(additionalParam), str(numRuns), str(testType), str(layout), '0', str(qaMode), str(decoderType), str(batchSize)] + roiList + [scriptPath], stdout=subprocess.PIPE, stderr=subprocess.PIPE) # nosec while True: output = process.stdout.readline() if not output and process.poll() is not None: @@ -272,7 +272,7 @@ def rpp_test_suite_parser_and_validator(): subprocess.call(["make", "-j16"], cwd=".") # nosec # List of cases supported -supportedCaseList = ['0', '1', '2', '4', '5', '6', '8', '13', '20', '21', '23', '26', '29', '30', '31', '32', '33', '34', '35', '36', '37', '38', '39', '45', '46', '54', '61', '63', '65', '68', '70', '79', '80', '82', '83', '84', '85', '86', '87', '88', '89', '90', '91', '92'] +supportedCaseList = ['0', '1', '2', '4', '5', '6', '8', '13', '20', '21', '23', '26', '29', '30', '31', '32', '33', '34', '35', '36', '37', '38', '39', '41', '45', '46', '54', '61', '63', '65', '68', '70', '79', '80', '82', '83', '84', '85', '86', '87', '88', '89', '90', '91', '92'] # Create folders based on testType and profilingOption if testType == 1 and profilingOption == "YES": diff --git a/utilities/test_suite/HOST/Tensor_host.cpp b/utilities/test_suite/HOST/Tensor_host.cpp index f370ca4d1..3c2a2ae0f 100644 --- a/utilities/test_suite/HOST/Tensor_host.cpp +++ b/utilities/test_suite/HOST/Tensor_host.cpp @@ -64,7 +64,8 @@ int main(int argc, char **argv) int decoderType = atoi(argv[13]); int batchSize = atoi(argv[14]); - bool additionalParamCase = (testCase == 8 || testCase == 21 || testCase == 23 || testCase == 24 || testCase == 79); + bool additionalParamCase = (testCase == 8 || testCase == 21 || testCase == 23 || testCase == 24 || testCase == 41 || testCase == 79); + bool kernelSizeCase = (testCase == 41); bool dualInputCase = (testCase == 2 || testCase == 30 || testCase == 33 || testCase == 61 || testCase == 63 || testCase == 65 || testCase == 68); bool randomOutputCase = (testCase == 6 || testCase == 8 || testCase == 84); bool nonQACase = (testCase == 24); @@ -211,6 +212,13 @@ int main(int argc, char **argv) func += "_noiseType"; func += noiseTypeName.c_str(); } + else if (kernelSizeCase) + { + char additionalParam_char[2]; + std::sprintf(additionalParam_char, "%u", additionalParam); + func += "_kernelSize"; + func += additionalParam_char; + } if(!qaFlag) { @@ -1068,6 +1076,20 @@ int main(int argc, char **argv) break; } + case 41: + { + testCaseName = "dilate"; + Rpp32u kernelSize = additionalParam; + + startWallTime = omp_get_wtime(); + startCpuTime = clock(); + if (inputBitDepth == 0 || inputBitDepth == 1 || inputBitDepth == 2 || inputBitDepth == 5) + rppt_dilate_host(input, srcDescPtr, output, dstDescPtr, kernelSize, roiTensorPtrSrc, roiTypeSrc, handle); + else + missingFuncFlag = 1; + + break; + } case 45: { testCaseName = "color_temperature"; @@ -1600,7 +1622,7 @@ int main(int argc, char **argv) 3.source and destination layout are the same 4.augmentation case does not generate random output*/ if(qaFlag && inputBitDepth == 0 && ((srcDescPtr->layout == dstDescPtr->layout) || pln1OutTypeCase) && !(randomOutputCase) && !(nonQACase)) - compare_output(outputu8, testCaseName, srcDescPtr, dstDescPtr, dstImgSizes, batchSize, interpolationTypeName, noiseTypeName, testCase, dst, scriptPath); + compare_output(outputu8, testCaseName, srcDescPtr, dstDescPtr, dstImgSizes, batchSize, interpolationTypeName, noiseTypeName, additionalParam, testCase, dst, scriptPath); // Calculate exact dstROI in XYWH format for OpenCV dump if (roiTypeSrc == RpptRoiType::LTRB) diff --git a/utilities/test_suite/HOST/runTests.py b/utilities/test_suite/HOST/runTests.py index 2f2d49d94..1c7067256 100644 --- a/utilities/test_suite/HOST/runTests.py +++ b/utilities/test_suite/HOST/runTests.py @@ -62,7 +62,12 @@ def run_unit_test(srcPath1, srcPath2, dstPathTemp, case, numRuns, testType, layo if layout == 2 and outputFormatToggle == 1: continue - if case == "8": + if case == "41": + for kernelSize in range(3, 10, 2): + print(f"./Tensor_host {srcPath1} {srcPath2} {dstPathTemp} {bitDepth} {outputFormatToggle} {case} {kernelSize} 0 ") + result = subprocess.run([buildFolderPath + "/build/Tensor_host", srcPath1, srcPath2, dstPathTemp, str(bitDepth), str(outputFormatToggle), str(case), str(kernelSize), str(numRuns), str(testType), str(layout), "0", str(qaMode), str(decoderType), str(batchSize)] + roiList + [scriptPath], stdout=subprocess.PIPE, stderr=subprocess.PIPE) # nosec + print(result.stdout.decode()) + elif case == "8": # Run all variants of noise type functions with additional argument of noiseType = gausssianNoise / shotNoise / saltandpepperNoise for noiseType in range(3): print("./Tensor_host " + srcPath1 + " " + srcPath2 + " " + dstPathTemp + " " + str(bitDepth) + " " + str(outputFormatToggle) + " " + str(case) + " " + str(noiseType) + " 0") @@ -80,8 +85,8 @@ def run_unit_test(srcPath1, srcPath2, dstPathTemp, case, numRuns, testType, layo stdout_data, stderr_data = result.communicate() print(stdout_data.decode()) else: - print("\n./Tensor_host " + srcPath1 + " " + srcPath2 + " " + dstPathTemp + " " + str(bitDepth) + " " + str(outputFormatToggle) + " " + str(case) + " 0 " + str(numRuns) + " " + str(testType) + " " + str(layout) + " 0") - result = subprocess.Popen([buildFolderPath + "/build/Tensor_host", srcPath1, srcPath2, dstPathTemp, str(bitDepth), str(outputFormatToggle), str(case), "0", str(numRuns), str(testType), str(layout), "0", str(qaMode), str(decoderType), str(batchSize)] + roiList + [scriptPath], stdout=subprocess.PIPE) # nosec + print("./Tensor_host " + srcPath1 + " " + srcPath2 + " " + dstPathTemp + " " + str(bitDepth) + " " + str(outputFormatToggle) + " " + str(case) + " 0 " + str(numRuns) + " " + str(testType) + " " + str(layout) + " 0") + result = subprocess.Popen([buildFolderPath + "/build/Tensor_host", srcPath1, srcPath2, dstPathTemp, str(bitDepth), str(outputFormatToggle), str(case), "0", str(numRuns), str(testType), str(layout), "0", str(qaMode), str(decoderType), str(batchSize)] + roiList + [scriptPath], stdout=subprocess.PIPE, stderr=subprocess.PIPE) # nosec stdout_data, stderr_data = result.communicate() print(stdout_data.decode()) @@ -90,12 +95,12 @@ def run_unit_test(srcPath1, srcPath2, dstPathTemp, case, numRuns, testType, layo def run_performance_test_cmd(loggingFolder, logFileLayout, srcPath1, srcPath2, dstPath, bitDepth, outputFormatToggle, case, additionalParam, numRuns, testType, layout, qaMode, decoderType, batchSize, roiList): if qaMode == 1: with open(loggingFolder + "/BatchPD_host_" + logFileLayout + "_raw_performance_log.txt", "a") as logFile: - process = subprocess.Popen([buildFolderPath + "/build/BatchPD_host_" + logFileLayout, srcPath1, srcPath2, str(bitDepth), str(outputFormatToggle), str(case), str(additionalParam), "0"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) # nosec + process = subprocess.Popen([buildFolderPath + "/build/BatchPD_host_" + logFileLayout, srcPath1, srcPath2, str(bitDepth), str(outputFormatToggle), str(case), str(additionalParam), "0"], stdout=subprocess.PIPE, stderr=subprocess.PIPE) # nosec read_from_subprocess_and_write_to_log(process, logFile) with open(loggingFolder + "/Tensor_host_" + logFileLayout + "_raw_performance_log.txt", "a") as logFile: logFile.write("./Tensor_host " + srcPath1 + " " + srcPath2 + " " + dstPath + " " + str(bitDepth) + " " + str(outputFormatToggle) + " " + str(case) + " " + str(additionalParam) + " 0\n") - process = subprocess.Popen([buildFolderPath + "/build/Tensor_host", srcPath1, srcPath2, dstPath, str(bitDepth), str(outputFormatToggle), str(case), str(additionalParam), str(numRuns), str(testType), str(layout), "0", str(qaMode), str(decoderType), str(batchSize)] + roiList + [scriptPath], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) # nosec + process = subprocess.Popen([buildFolderPath + "/build/Tensor_host", srcPath1, srcPath2, dstPath, str(bitDepth), str(outputFormatToggle), str(case), str(additionalParam), str(numRuns), str(testType), str(layout), "0", str(qaMode), str(decoderType), str(batchSize)] + roiList + [scriptPath], stdout=subprocess.PIPE, stderr=subprocess.PIPE) # nosec read_from_subprocess_and_write_to_log(process, logFile) def run_performance_test(loggingFolder, logFileLayout, srcPath1, srcPath2, dstPath, case, numRuns, testType, layout, qaMode, decoderType, batchSize, roiList): @@ -108,7 +113,10 @@ def run_performance_test(loggingFolder, logFileLayout, srcPath1, srcPath2, dstPa # There is no layout toggle for PLN1 case, so skip this case if layout == 2 and outputFormatToggle == 1: continue - if case == "8": + if case == "41": + for kernelSize in range(3, 10, 2): + run_performance_test_cmd(loggingFolder, logFileLayout, srcPath1, srcPath2, dstPath, bitDepth, outputFormatToggle, case, kernelSize, numRuns, testType, layout, qaMode, decoderType, batchSize, roiList) + elif case == "8": # Run all variants of noise type functions with additional argument of noiseType = gausssianNoise / shotNoise / saltandpepperNoise for noiseType in range(3): run_performance_test_cmd(loggingFolder, logFileLayout, srcPath1, srcPath2, dstPath, bitDepth, outputFormatToggle, case, noiseType, numRuns, testType, layout, qaMode, decoderType, batchSize, roiList) @@ -252,7 +260,7 @@ def rpp_test_suite_parser_and_validator(): subprocess.call(["make", "-j16"], cwd=".") # nosec # List of cases supported -supportedCaseList = ['0', '1', '2', '4', '5', '6', '8', '13', '20', '21', '23', '26', '29', '30', '31', '32', '33', '34', '35', '36', '37', '38', '39', '45', '46', '54', '61', '63', '65', '68', '70', '79', '80', '81', '82', '83', '84', '85', '86', '87', '88', '89', '90', '91', '92'] +supportedCaseList = ['0', '1', '2', '4', '5', '6', '8', '13', '20', '21', '23', '26', '29', '30', '31', '32', '33', '34', '35', '36', '37', '38', '39', '41', '45', '46', '54', '61', '63', '65', '68', '70', '79', '80', '81', '82', '83', '84', '85', '86', '87', '88', '89', '90', '91', '92'] if testType == 0: noCaseSupported = all(case not in supportedCaseList for case in caseList) diff --git a/utilities/test_suite/common.py b/utilities/test_suite/common.py index e24ee73f6..c6da10c76 100644 --- a/utilities/test_suite/common.py +++ b/utilities/test_suite/common.py @@ -60,6 +60,7 @@ 37: ["crop", "HOST", "HIP"], 38: ["crop_mirror_normalize", "HOST", "HIP"], 39: ["resize_crop_mirror", "HOST", "HIP"], + 41: ["dilate", "HOST", "HIP"], 45: ["color_temperature", "HOST", "HIP"], 46: ["vignette", "HOST", "HIP"], 49: ["box_filter", "HIP"], @@ -120,7 +121,8 @@ "arithmetic_operations" : [61], "logical_operations" : [65, 68], "data_exchange_operations" : [70, 85, 86], - "statistical_operations" : [87, 88, 89, 90, 91] + "statistical_operations" : [87, 88, 89, 90, 91], + "morphological_operations" : [41] } # Checks if the folder path is empty, or is it a root folder, or if it exists, and remove its contents diff --git a/utilities/test_suite/rpp_test_suite_common.h b/utilities/test_suite/rpp_test_suite_common.h index c4feec51c..d4a131537 100644 --- a/utilities/test_suite/rpp_test_suite_common.h +++ b/utilities/test_suite/rpp_test_suite_common.h @@ -96,6 +96,7 @@ std::map augmentationMap = {37, "crop"}, {38, "crop_mirror_normalize"}, {39, "resize_crop_mirror"}, + {41, "dilate"}, {45, "color_temperature"}, {46, "vignette"}, {49, "box_filter"}, @@ -521,7 +522,7 @@ inline void set_generic_descriptor_slice(RpptDescPtr srcDescPtr, RpptGenericDesc } // sets descriptor dimensions and strides of src/dst -inline void set_descriptor_dims_and_strides(RpptDescPtr descPtr, int noOfImages, int maxHeight, int maxWidth, int numChannels, int offsetInBytes) +inline void set_descriptor_dims_and_strides(RpptDescPtr descPtr, int noOfImages, int maxHeight, int maxWidth, int numChannels, int offsetInBytes, int additionalStride = 0) { descPtr->numDims = 4; descPtr->offsetInBytes = offsetInBytes; @@ -531,7 +532,7 @@ inline void set_descriptor_dims_and_strides(RpptDescPtr descPtr, int noOfImages, descPtr->c = numChannels; // Optionally set w stride as a multiple of 8 for src/dst - descPtr->w = ((descPtr->w / 8) * 8) + 8; + descPtr->w = (descPtr->w / 8) * 8 + 8 + additionalStride; // set strides if (descPtr->layout == RpptLayout::NHWC) { @@ -1096,7 +1097,7 @@ void compare_outputs_pln3(Rpp8u* output, Rpp8u* refOutput, RpptDescPtr dstDescPt } template -inline void compare_output(T* output, string funcName, RpptDescPtr srcDescPtr, RpptDescPtr dstDescPtr, RpptImagePatch *dstImgSizes, int noOfImages, string interpolationTypeName, string noiseTypeName, int testCase, string dst, string scriptPath) +inline void compare_output(T* output, string funcName, RpptDescPtr srcDescPtr, RpptDescPtr dstDescPtr, RpptImagePatch *dstImgSizes, int noOfImages, string interpolationTypeName, string noiseTypeName, int additionalParam, int testCase, string dst, string scriptPath) { string func = funcName; string refFile = ""; @@ -1113,7 +1114,7 @@ inline void compare_output(T* output, string funcName, RpptDescPtr srcDescPtr, R } int refOutputSize = refOutputHeight * refOutputWidth * dstDescPtr->c; Rpp64u binOutputSize = refOutputHeight * refOutputWidth * dstDescPtr->n * 4; - int pln1RefStride = dstDescPtr->strides.nStride * dstDescPtr->n * 3; + int pln1RefStride = refOutputHeight * refOutputWidth * dstDescPtr->n * 3; string dataType[4] = {"_u8_", "_f16_", "_f32_", "_i8_"}; @@ -1157,6 +1158,11 @@ inline void compare_output(T* output, string funcName, RpptDescPtr srcDescPtr, R func += "_noiseType" + noiseTypeName; binFile += "_noiseType" + noiseTypeName; } + else if(testCase == 49 || testCase == 41) + { + func += "_kernelSize" + std::to_string(additionalParam); + binFile += "_kernelSize" + std::to_string(additionalParam); + } refFile = scriptPath + "/../REFERENCE_OUTPUT/" + funcName + "/"+ binFile + ".bin"; int fileMatch = 0; From 5d9a40f9861764b803f1db5475dedb1528b4cac4 Mon Sep 17 00:00:00 2001 From: sampath1117 Date: Thu, 5 Sep 2024 14:57:20 +0000 Subject: [PATCH 2/3] minor fix in changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4464281a8..4f9f73cab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ Full documentation for RPP is available at [https://rocm.docs.amd.com/projects/r ### Changes -* RPP Tensor Erode support on HOST +* RPP Tensor Dilate support on HOST ## RPP 1.9.1 for ROCm 6.3.0 From d1055938727f4752e4cecf6f2d65afcd4c7955af Mon Sep 17 00:00:00 2001 From: sampath1117 Date: Fri, 6 Sep 2024 11:49:02 +0000 Subject: [PATCH 3/3] added golden outputs remove commented code --- src/modules/cpu/kernel/dilate.hpp | 3 --- .../dilate/dilate_u8_Tensor_kernelSize3.bin | Bin 0 -> 273600 bytes .../dilate/dilate_u8_Tensor_kernelSize5.bin | Bin 0 -> 273600 bytes .../dilate/dilate_u8_Tensor_kernelSize7.bin | Bin 0 -> 273600 bytes .../dilate/dilate_u8_Tensor_kernelSize9.bin | Bin 0 -> 273600 bytes 5 files changed, 3 deletions(-) create mode 100644 utilities/test_suite/REFERENCE_OUTPUT/dilate/dilate_u8_Tensor_kernelSize3.bin create mode 100644 utilities/test_suite/REFERENCE_OUTPUT/dilate/dilate_u8_Tensor_kernelSize5.bin create mode 100644 utilities/test_suite/REFERENCE_OUTPUT/dilate/dilate_u8_Tensor_kernelSize7.bin create mode 100644 utilities/test_suite/REFERENCE_OUTPUT/dilate/dilate_u8_Tensor_kernelSize9.bin diff --git a/src/modules/cpu/kernel/dilate.hpp b/src/modules/cpu/kernel/dilate.hpp index 877043ba4..98870ec8d 100644 --- a/src/modules/cpu/kernel/dilate.hpp +++ b/src/modules/cpu/kernel/dilate.hpp @@ -224,9 +224,6 @@ inline void max_rows_9x9(__m256 *pRow, __m256 *pDst) pDst[0] = _mm256_max_ps(pDst[0], _mm256_max_ps(_mm256_max_ps(pRow[6], pRow[7]), pRow[8])); } -// #undef __AVX2__ -// #define __AVX2__ 0 - template RppStatus dilate_char_host_tensor(T *srcPtr, RpptDescPtr srcDescPtr, diff --git a/utilities/test_suite/REFERENCE_OUTPUT/dilate/dilate_u8_Tensor_kernelSize3.bin b/utilities/test_suite/REFERENCE_OUTPUT/dilate/dilate_u8_Tensor_kernelSize3.bin new file mode 100644 index 0000000000000000000000000000000000000000..c1203ba04b024256db02c98a817eea26d63d8ed5 GIT binary patch literal 273600 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 DqPv_< literal 0 HcmV?d00001 diff --git a/utilities/test_suite/REFERENCE_OUTPUT/dilate/dilate_u8_Tensor_kernelSize5.bin b/utilities/test_suite/REFERENCE_OUTPUT/dilate/dilate_u8_Tensor_kernelSize5.bin new file mode 100644 index 0000000000000000000000000000000000000000..1bed0341d45b8225593e80d16f4f9815b334fd01 GIT binary patch literal 273600 zcmeF)cT`j9`tbeduaU8h3W!ofdanry5JCt92vvHKUPO>0Ql&{pP&(2(DxlJoB32Z^ z0*H!@QLrFLw=3%S4DY?OL)btp^DEC{&bw!QR?a<y^waN?{H7GvyRi(3|Cwr1h2eD8t)^e;3uROBxVw$ zXd9>He$aSVvhm&m`-Jj_`agc^djK(i^49VLAFjAr&|6xG-m%Aix1DNmQcRG5e+7}#; zZ@y9njJ@ecdj7KS#@&m+wYwME`&vs+X8}LnN3`RAE!?7i7u?Y;(a1f~*yn(y&p}I{ z6bQIfcQ5Q1ob7d}Dk-}vDW@uFaNs%&84;XcPd`C=Iq~PyvHMO5S^=5%L7A>G#Q|A0 zaY$h~zKc5UOy0ILS%1+(qJR3+3ApSxggM0O?L468eo)UR#n>msbWz8>*d=fG``SjW3AaLVBdvxxu=v?{P=jv+Ztw3;BO$KGs+d$J{1MzrIiM!)i> z$c|4^GK3Kq=s$0p5uNa=Yu9!ADss7J!%I)#AkyyCVsW!%ju~JhN-EE7o#ux zpFV8tI^5V*(b!eACrtCnqU9hhn-vq{<-=AAXu$5pRf6_6pF&s#$_S%_Dc0+JEKtw(v zdj6FO?1^#M6X&oe!5QY0;0$=hIlDwSwstpo~lRjE+w6s*dqkdmsU8n;;MT%9EpCgPcHEoL5-9 zci8^jNI`MlmwMYqUk|@}J^YSgdidSs^EU%`-`wk%x_4t5xXo~B>UP_7cN486NV%lr z>!~+1)31l8Uk^PQy9Msm`HnHFmJu=*5#m;HQZVEbqUT?kKzvfzfz+tf%s8aNviz}$ zk?GeXuU?OQob!2l`19oR&jWWq-|P5%@5(fA8+LK}_PMFs=cc<)P2X&t?(3L-cys#M z<7wnRF6=lqG5B!cdegWdh&p`03Ro(#MIB z&qEJC58b16Y+-clKY0j%J2GAe&s4yMuAh9-cYX3v-{jE1hKH z09adbXj^f_;F(ew=s0x!G}Q6YUEtmDAQEFA;g0Q%Wu-as=sq&d^wT$kLwtacFrTmp zUs(Ia=9i%3_%PDj7mp_HUZ1#qdEnCV$8A*)PUgb}=Q;*oI0T_}4DGCl>#a$L-EOWJ z>S-H$&gD6~)5k`?V9^BjB?QCL zQljtPxG*-?@$>i~-RR)w(LqMXN0*M>Y0Mw!YzCg)xbm#~>hqp!Lvu!Zu8sCw8SQDG zyal^Fc?;5iwrcoh=l423xOD37rITPEK}SCy=baw5iyjjF)1OWNj#!wR+E88m^1+Rt zcD#GK{M*D6q~~{fhG4h5M}eCiOFIsCo#{V&{7Ku1XB{m7V-w-Sqf2$*zaDVMeS3Gg zxmx}7vC%JBGy%}DAS(%Ue17}t4?2!Ly!Gr#^Rp|hvlCB|K+s?8IMCh*IzlctF6nsu z(WNRzM_*sJMGuMo=}#vBcf5J7eu&ob)pzby(DC89laD+8TOGj%W!H)PCztDy1}@cu zh(LQ?5x7^uKKh{@f0}K{7l4j+Wm%6pT0qCi2RAtV zqfW`9yPc!A+rQWGep?H0x2@&;sgun|$}U%C z0oQ9`g`gc0qvQD#h38M?pD&2ISaS&JL|HPpRMC5tX{jH-Ip49TZTe2f(vBlH&W(4q zFgn7Au^UZ~JDTn{ABVM+We524MRv?C)TI50Z49!ZB?I+}jzXv(F^ z%uAJruGE%-yOdE7K7fu#DpEi@q&Q^>Uafd z{ASx&$H~X7N11fIUeB~6SV@Me<>ij zLdpqqZ7PatD%lToG#z2OkNHJm`9*v4ivm)L{Gto|qVo4d=J^0i9us}zClg4@j!4Vh z*U?nm-+lrZyWY4!d+pu&d}Ks$?yWDlab*9cynQw4p|IkFfCMY61YhSvp&>{`F*||c zq&+QFhZc2gI+lB+DkZgQUusoYN>%WIs=%bGz{DeaU_W_i^oN&BAgMSar6M}xXv~$y zeBgOoCDOIxc%~iOOX5Jsc=v4w-K{d*9nZw2BAp10Dxd{T_S>EwWCt`9Cbpc&Z>h;| zsiiwon*yXB- zjwN;+iv%6%zn%vFs#@ELD@W2m#|wG;qU|&eT3NNECSNQlLOKy2SCSY)6Ht-}_=Aqk z$8wu%=atnar`E=z9eZ zFBHdvjtTY}2kn`4%sX%dmK}Q}JEr77KyiUz{;`Ps+UWfHSXgF#Y$|~6<1d%`@fW6_ znD1EJ5CfDzqDtzcDjL(;uQdQS89HijcGO-zceJrMz9wZ)ZlFn)@2__ZgFEKc$L7|@ zW}qE^9OfVXXnM_j$Lxlf?1s4F#x$U$G5zw@dbs2KYe#K6t>f9^__GB8bC%qz%=Z!Q z7@b!iom(HBQ6HUBzc1zZ-sI!{`;YIAr%62S4f}_G5$KoypRAgQth$Jjy8S(O&JH}e zJ^1tJEN<1M$FlF*m2)s zI{*#yPbV-u)(N~EyYq77-pi5u!05=}=(EAmr*j5D-rj-U&Vg%Zt}gBvakeb^Y(?^! zIZYMGM;Q^pf%f@WbRZxj#X0Nf9!NdifqD-h?5KSxLS%(&WI5!Y{#Bq~KRkAQc&zKy z*u7UHzhZdBQ!a9BZ_Kai8;k&*76QRV?T zLKl0)Ir4}@P?=S5nMH6ZB(&5n{)h(*?eb42FgbP`_@$0*XS$A;o+(Z{oESLw$;h;0 zc;>-92P5}n!1j4ooI8q?UgDls<`z-5J+#akbc{F*vyV9Jh<5y^`50gSPdi>LOS@Q> z*pL}iog7e`8F8j;{!#_ED|D~shG(WlMWsb$WY8faf^+)Gu*5RwlzM-pglf-}xuO8rz@ayQ^p3yrUz|hsx zL+vMr&Nl#$T24G_Dh2u)vagh-0N`GQJ66W*DhPL;Gv5)4Nb9)wyB)*ej#&*+z@m<6 zP5XZJ>Cvy7o51+(b_VeGZ;W(Z818HwYC8rwa~OalGVNHE=mjebcPk91cifvAwl^&b z2+W8|%81G>h=-h{OFJ2sb0!uE?Kqd>zxA(U-Qf2e=|--c1cuI651l&

@39kPQR- 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 W91YGOwV;=ivopZlky*=p;{OG7f84JC literal 0 HcmV?d00001 diff --git a/utilities/test_suite/REFERENCE_OUTPUT/dilate/dilate_u8_Tensor_kernelSize7.bin b/utilities/test_suite/REFERENCE_OUTPUT/dilate/dilate_u8_Tensor_kernelSize7.bin new file mode 100644 index 0000000000000000000000000000000000000000..2f97be99d511cba201a78b1e817aea9c20cf3c9b GIT binary patch literal 273600 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

8kblKwl?ODKZgWXFj^j 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