From 9bd5f0813ca30056ee5aa9b8e03e2c40a8d38c82 Mon Sep 17 00:00:00 2001 From: Abishek <52214183+r-abishek@users.noreply.github.com> Date: Sat, 23 Mar 2024 00:32:55 -0700 Subject: [PATCH] RPP Vignette Tensor on HOST and HIP (#311) * Add Vignette Tensor HOST and HIP Implementation * License - updates to 2024 and consistency changes (#298) * Match all CMakeLists.txt license as per RPP's outermost LICENSE file * Match all python files' license as per RPP's outermost LICENSE file * Match all .hpp files' license as per RPP's outermost LICENSE file * Match all .cpp files' license as per RPP's outermost LICENSE file * Match all .h files' license as per RPP's outermost LICENSE file * Remove all rights reserved as per LICENSE file * Remove double space in "Copyright (c) 2019 - 2023 Advanced Micro Devices, Inc." * Match all .cmake files' license as per RPP's outermost LICENSE file * Match all .cpp.in files' license as per RPP's outermost LICENSE file * Replace 283 occurrences in 282 files - 2023 to 2024 * Add "MIT License" title to 281 instances * Add missing license * Test - Update README.md for test_suite (#299) * Bump rocm-docs-core[api_reference] from 0.33.0 to 0.33.1 in /docs/sphinx (#301) Bumps [rocm-docs-core[api_reference]](https://github.com/RadeonOpenCompute/rocm-docs-core) from 0.33.0 to 0.33.1. - [Release notes](https://github.com/RadeonOpenCompute/rocm-docs-core/releases) - [Changelog](https://github.com/ROCm/rocm-docs-core/blob/develop/CHANGELOG.md) - [Commits](https://github.com/RadeonOpenCompute/rocm-docs-core/compare/v0.33.0...v0.33.1) --- updated-dependencies: - dependency-name: rocm-docs-core[api_reference] dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Bump rocm-docs-core[api_reference] from 0.33.1 to 0.33.2 in /docs/sphinx (#302) Bumps [rocm-docs-core[api_reference]](https://github.com/RadeonOpenCompute/rocm-docs-core) from 0.33.1 to 0.33.2. - [Release notes](https://github.com/RadeonOpenCompute/rocm-docs-core/releases) - [Changelog](https://github.com/ROCm/rocm-docs-core/blob/develop/CHANGELOG.md) - [Commits](https://github.com/RadeonOpenCompute/rocm-docs-core/compare/v0.33.1...v0.33.2) --- updated-dependencies: - dependency-name: rocm-docs-core[api_reference] dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Update doc codeowners (#303) * Documentation - Bump rocm-docs-core[api_reference] from 0.33.2 to 0.34.0 in /docs/sphinx (#304) Bumps [rocm-docs-core[api_reference]](https://github.com/RadeonOpenCompute/rocm-docs-core) from 0.33.2 to 0.34.0. - [Release notes](https://github.com/RadeonOpenCompute/rocm-docs-core/releases) - [Changelog](https://github.com/ROCm/rocm-docs-core/blob/develop/CHANGELOG.md) - [Commits](https://github.com/RadeonOpenCompute/rocm-docs-core/compare/v0.33.2...v0.34.0) --- updated-dependencies: - dependency-name: rocm-docs-core[api_reference] dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Test suite - upgrade 5 qa perf (#305) * experimental changes for adding qa mode for performance tests * made changes to add display more information w.r.t QA results summary for performance tests * minor changes * Add changes to dump qa results to excel file * Add performance QA for three new tensor functions * update prerequisites in readme * added changes to handle unsupported cases * removed treshold dictionary and added performance Noise treshold add new dataset for performance QA * RPP Test Suite Upgrade 4 - CSV to BIN conversions for file size reduction (#293) * change golden outputs from .csv files to .bin files * Changed comparision funtions to use .bin files * Address review comments * minor change * Address review comments * minor change --------- Co-authored-by: HazarathKumarM * Changes to the performane summary dataframe * minor changes * Update CMakeLists.txt to add ${CMAKE_CURRENT_SOURCE_DIR} for CI * Update CMakeLists.txt fix * Update CMakeLists.txt fix * remove tabulate dependency * Update README.md to remove tabulate pip install * Fix for CI machine failure * Add note on performance --------- Co-authored-by: sampath1117 Co-authored-by: HazarathKumarM Co-authored-by: Abishek <52214183+r-abishek@users.noreply.github.com> Co-authored-by: Snehaa Giridharan Co-authored-by: r-abishek * RPP Color Temperature on HOST and HIP (#271) * Initial commit - Color Temperature HOST Tensor * Initial commit - Color Temperature HIP Tensor * Add color temperature golden outputs * address review comments * Use reinterpret_cast instead of static_cast * Combine templated functions to support all datatypes into one (got minor perf difference of order 3%) Also fixes indentation * Fix i8 datatype * Cleanup * RPP Test Suite Upgrade 4 - CSV to BIN conversions for file size reduction (#293) * change golden outputs from .csv files to .bin files * Changed comparision funtions to use .bin files * Address review comments * minor change * Address review comments * minor change --------- Co-authored-by: HazarathKumarM * Fix PLN3 variant outputs Also modifies reference outputs * Update color_temperature.hpp license * Delete color_temperature_u8_Tensor_PKD3.csv * Delete color_temperature_u8_Tensor_PLN3.csv --------- Co-authored-by: snehaa8 Co-authored-by: HazarathKumarM Co-authored-by: Snehaa-Giridharan <118163708+snehaa8@users.noreply.github.com> * RPP Voxel 3D Tensor Add/Subtract scalar on HOST and HIP (#272) * added HOST support for voxel add kernel * added HIP support for voxel add kernel * added test suite support for add scalar * added Doxygen support and modified hip kernel function names as per new standard * added HOST support for voxel subtract kernel * added HIP support for voxel subtract kernel * added test suite support * updated the golden outputs for subtract with correct values * removed unnessary validation checks * Remove double spaces * Fix header * Fix all retval docs * Fix docs to add memory type * Fix comment * Add divider comment * Use post-increment efficiently * RPP Test Suite Upgrade 4 - CSV to BIN conversions for file size reduction (#293) * change golden outputs from .csv files to .bin files * Changed comparision funtions to use .bin files * Address review comments * minor change * Address review comments * minor change --------- Co-authored-by: HazarathKumarM * converted add and subtract scalar golden outputs to bin files * changed copyright from 2023 to 2024 * Update add_scalar.hpp license * Update subtract_scalar.hpp license --------- Co-authored-by: sampath1117 Co-authored-by: HazarathKumarM * RPP Magnitude on HOST and HIP (#278) * Initial commit - Magnitude HOST Tensor * Add QA reference outputs * Update runTests.py * Initial commit - Magnitude HIP Tensor * Add dual input support in testsuite * Optimize HOST kernel further * Optimize i8 datatype further * Modify comments * RPP Test Suite Upgrade 4 - CSV to BIN conversions for file size reduction (#293) * change golden outputs from .csv files to .bin files * Changed comparision funtions to use .bin files * Address review comments * minor change * Address review comments * minor change --------- Co-authored-by: HazarathKumarM * Bump rocm-docs-core[api_reference] from 0.31.0 to 0.33.0 in /docs/sphinx (#294) Bumps [rocm-docs-core[api_reference]](https://github.com/RadeonOpenCompute/rocm-docs-core) from 0.31.0 to 0.33.0. - [Release notes](https://github.com/RadeonOpenCompute/rocm-docs-core/releases) - [Changelog](https://github.com/ROCm/rocm-docs-core/blob/develop/CHANGELOG.md) - [Commits](https://github.com/RadeonOpenCompute/rocm-docs-core/compare/v0.31.0...v0.33.0) --- updated-dependencies: - dependency-name: rocm-docs-core[api_reference] dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Update Copywright year * Combine templated functions to support all datatypes * Modify format of reference outputs * Update rppi_arithmetic_operations.h license * Update rppt_tensor_arithmetic_operations.h license * Update host_tensor_arithmetic_operations.hpp * Update magnitude.hpp license * Update hip_tensor_arithmetic_operations.hpp license * Delete magnitude_u8_Tensor_PKD3.csv * Delete magnitude_u8_Tensor_PLN1.csv * Delete magnitude_u8_Tensor_PLN3.csv * Update rpp_test_suite_common.h license * Update runTests.py license * Update Tensor_hip.cpp license * Update runTests.py license * Update Tensor_host.cpp license --------- Signed-off-by: dependabot[bot] Co-authored-by: Snehaa Giridharan Co-authored-by: HazarathKumarM Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Snehaa-Giridharan <118163708+snehaa8@users.noreply.github.com> * Bump rocm-docs-core[api_reference] from 0.34.0 to 0.34.2 in /docs/sphinx (#309) Bumps [rocm-docs-core[api_reference]](https://github.com/RadeonOpenCompute/rocm-docs-core) from 0.34.0 to 0.34.2. - [Release notes](https://github.com/RadeonOpenCompute/rocm-docs-core/releases) - [Changelog](https://github.com/ROCm/rocm-docs-core/blob/develop/CHANGELOG.md) - [Commits](https://github.com/RadeonOpenCompute/rocm-docs-core/compare/v0.34.0...v0.34.2) --- updated-dependencies: - dependency-name: rocm-docs-core[api_reference] dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * RPP Tensor Audio Support - Down Mixing (#296) * Initial commit - Non slient region detection Includes unittest setup * Initial commit - To Decibels Includes unittest setup * Intial commit - pre_emphasis_filter * Intial commit - down_mixing * Replace vectors with arrays * Cleanup * Minor cleanup * Optimize downmixing Kernel Includes cleanup * Replace Rpp64s with Rpp32s * Cleanup * Optimize and precompute cutOff * Fix buffer used * Fix buffer used * Additional Cleanup * Optimize post incrmeent operation * Optimize post increment operation * Update testsuite for Audio * code cleanup * Add Readme file for Audio test suite * changes based on review comments * minor change * Remove unittest folders and updated README.md * Remove unit tests * minor change * code cleanup * added common header file for audio helper functions * removed unncessary audio wav files fixed bug in ROI updation for audio test suite resolved issue in summary generation for performance tests in python * removed log file * added doxygen support for audio * added doxygen changes for to_decibels * updated test suite support for to_decibels * minor change * added doxygen changes for preemphasis filter * updated changes for preemphasis filter in test suite * removed the usage of getMax function and used std::max_element * modularized code in test suite * merge with latest changes * minor change * minor change * minor change * resolved codacy warnings * Codacy fix - Remove unused cpuTime * CMakeLists - Version Update 1.5.0 - TOT Version * CHANGELOG Updates Version 1.5.0 placeholder * resolved issue with file_system dependency in test suite * Doxygen changes changed malloc to new in NSR kernel * RPP RICAP Tensor for HOST and HIP (#213) * Initial commit - Ricap HOST Tensor Includes testsuite changes * Add QA tests for RICAP Used three_images_224x224_src1 folder to create golden outputs * Add three_images_224x224_src1 into TEST_IMAGES * Support HIP Backend for RICAP * Fix HIP pkd3->pkd3 variant * regenerated golden outputs for RICAP minor changes in HOST shell script for handling RICAP in QA mode * minor bug fix in RICAP HIP kernels * Improve readability and Cleanup * Additional cleanup * Cleanup testsuite Includes new golden outputs * Additional testuite fixes * Minor cleanup * Fix codacy warnings * Address other codacy warnings * Update ricap.hpp with reference paper * Add RICAP dataset path in readme * Make changes to error codes returned * Modify roi crop region for unit and perf tests * RPP Tensor Water Augmentation on HOST and HIP (#181) * added water HOST and HIP codes * added water case in test suite * added golden outputs for water * added omp thread changes for water augmentation * experimental changes * fixed output issue with AVX2 instructions * added AVX2 support for PKD3 load function minor changes in PLN variant load functions * nwc commit - added avx2 changes for u8 layout toggle variants but need to add store functions for completion * Add Avx2 implementation for F32 and U8 toggle variants * Add AVX2 support for u8 pkd3-pln3 and i8 pkd3-pln3 for water augmentation * change F32 load and store logic * optimized the store function for F32 PLN3-PKD3 * reverted back irrelevant changes * minor change * optimized load and store functions for water U8 and F32 variants in host removed commented code * removed golden outputs for water * minor changes * renamed few functions and removed unused functions updated i8 pln1 load as per the optimized u8 pln1 load * fixed bug in i8 load function * changed cast to c++ style resolved spacing issues and added comments for AVX codes for better understanding made changes to handle cases where QA Tests are not supported * added golden outputs for water * updated golden outputs with latest changes * modified the u8, i8 pkd3-pln3 function and added comments for the vectorized code * fixed minor bug in I8 variants * made to changes to resolve codacy warnings * changed cast to c++ style in hip kernel * changed generic nn F32 loads using gather and setr instructions * added comments for latest changes * minor change * added definition for storing 32 and 64 bits from a 128bit register --------- Co-authored-by: sampath1117 Co-authored-by: HazarathKumarM * Fix build error * CMakeLists - Version Update 1.5.0 - TOT Version * CHANGELOG Updates Version 1.5.0 placeholder * Boost deps fix for test suite --------- Co-authored-by: Snehaa Giridharan Co-authored-by: sampath1117 Co-authored-by: Snehaa-Giridharan <118163708+snehaa8@users.noreply.github.com> Co-authored-by: HazarathKumarM Co-authored-by: Kiriti Gowda * Documentation - Readme & changelog updates (#251) * readme and changelog updates for 6.0 * minor update * added ctests for audio test suite for CI made changes to add more clarity on the QA Tests results * Cmake mods for ctest * HOST-only build error bugfix * added qa mode paramter to python audio script added golden output map for QA testing of Non silent region detection * minor change * Documentation - Bump rocm-docs-core[api_reference] from 0.26.0 to 0.27.0 in /docs/sphinx (#253) Bumps [rocm-docs-core[api_reference]](https://github.com/RadeonOpenCompute/rocm-docs-core) from 0.26.0 to 0.27.0. - [Release notes](https://github.com/RadeonOpenCompute/rocm-docs-core/releases) - [Changelog](https://github.com/RadeonOpenCompute/rocm-docs-core/blob/develop/CHANGELOG.md) - [Commits](https://github.com/RadeonOpenCompute/rocm-docs-core/compare/v0.26.0...v0.27.0) --- updated-dependencies: - dependency-name: rocm-docs-core[api_reference] dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * RPP Resize Mirror Normalize Bugfix (#252) * added fix for hipMemset * remove pixel check for U8-F32 and U8-F16 for HOST codes --------- Co-authored-by: sampath1117 * added example for MMS calculation in comments for better understanding * Sphinx - updates (#257) * Sphinx - updates * Doxygen - Updates * Docs - Remove index.md * updated info used to for running audio test suite * removed bitdepth variable from audio test suite * added more information on computing NSR outputs in the example added * Fix doxygen for decibels Also removes extra QA reference files * move tensor_host_audio.cpp to host folder * Fix build errors and qa tests in Audio Test suite * Fix build errors and qa tests in Audio Test suite * Add reference output and test samples for downmix * Add down_mix in augmentation list and supported cases * Remove auto-merge repeated funcs * Improve clarity of header docs * Remove blank line * Improve clarity on header docs * Add Doxygen comments * minor change * converted golden outputs to binary file for downmixing * removed old golden output file for preemphasis and todecibels * modified info for downmixing as per new changes used handle memory for temporary buffers * formatting changes * moved the common code for SSE and AVX to outside * Update down_mixing.hpp license * Update rppt_tensor_audio_augmentations.h * combined the srcLength and channels tensors into single tensor --------- Signed-off-by: dependabot[bot] Co-authored-by: Snehaa Giridharan Co-authored-by: HazarathKumarM Co-authored-by: sampath1117 Co-authored-by: Kiriti Gowda Co-authored-by: Snehaa-Giridharan <118163708+snehaa8@users.noreply.github.com> Co-authored-by: Lisa Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Sundarrajan98 * RPP Voxel 3D Tensor Multiply scalar on HOST and HIP (#306) * added HIP support for voxel scalar multiply kernel * added HOST support for voxel multiply kernel added golden outputs for voxel multiply kernel * merge with master * RPP Test Suite Upgrade 4 - CSV to BIN conversions for file size reduction (#293) * change golden outputs from .csv files to .bin files * Changed comparision funtions to use .bin files * Address review comments * minor change * Address review comments * minor change --------- Co-authored-by: HazarathKumarM * converted multiply scalar voxel golden outputs to bin files * changed copyright from 2023 to 2024 --------- Co-authored-by: sampath1117 Co-authored-by: HazarathKumarM * Test Suite Bugfix (#307) * experimental changes for adding qa mode for performance tests * made changes to add display more information w.r.t QA results summary for performance tests * minor changes * Add changes to dump qa results to excel file * Add performance QA for three new tensor functions * update prerequisites in readme * added changes to handle unsupported cases * removed treshold dictionary and added performance Noise treshold add new dataset for performance QA * RPP Test Suite Upgrade 4 - CSV to BIN conversions for file size reduction (#293) * change golden outputs from .csv files to .bin files * Changed comparision funtions to use .bin files * Address review comments * minor change * Address review comments * minor change --------- Co-authored-by: HazarathKumarM * Changes to the performane summary dataframe * minor changes * Update CMakeLists.txt to add ${CMAKE_CURRENT_SOURCE_DIR} for CI * Update CMakeLists.txt fix * Update CMakeLists.txt fix * remove tabulate dependency * Update README.md to remove tabulate pip install * Fix for CI machine failure * Add note on performance * Fix segmentation fault * Revert QAmode to restrict HIP bitdepths * Use Rpp64u for HOST while comparing outputs * Fix ambiguous abs call * Fix for SLES CI HIP fail - error: incompatible pointer types assigning to 'unsigned long *' from 'unsigned long long *' - refOutput = TensorSumReferenceOutputs[numChannels].data(); --------- Co-authored-by: sampath1117 Co-authored-by: HazarathKumarM Co-authored-by: Snehaa Giridharan Co-authored-by: Pavel Tcherniaev * Add Vignette Tensor HOST and HIP Implementation * Address review comments * Update rpp_hip_common.hpp * Update vignette.hpp to add rpp_hip_math_nearbyintf8() * Update Tensor_hip.cpp to add hipHostFree * Fix init * Repeated initialization bugfix * Add host case 46 --------- Signed-off-by: dependabot[bot] Co-authored-by: HazarathKumarM Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Sam Wu Co-authored-by: Kiriti Gowda Co-authored-by: sampath1117 Co-authored-by: Snehaa Giridharan Co-authored-by: Snehaa-Giridharan <118163708+snehaa8@users.noreply.github.com> Co-authored-by: Lisa Co-authored-by: Sundarrajan98 Co-authored-by: Pavel Tcherniaev --- include/rppt_tensor_effects_augmentations.h | 44 + src/include/cpu/rpp_cpu_common.hpp | 44 + src/include/hip/rpp_hip_common.hpp | 16 +- .../cpu/host_tensor_effects_augmentations.hpp | 1 + src/modules/cpu/kernel/vignette.hpp | 1321 +++++++++++++++++ .../hip/hip_tensor_effects_augmentations.hpp | 1 + src/modules/hip/kernel/vignette.hpp | 306 ++++ .../rppt_tensor_effects_augmentations.cpp | 130 ++ utilities/test_suite/HIP/Tensor_hip.cpp | 21 + utilities/test_suite/HIP/runTests.py | 3 +- utilities/test_suite/HOST/Tensor_host.cpp | 17 + utilities/test_suite/HOST/runTests.py | 2 +- utilities/test_suite/README.md | 4 + .../vignette/vignette_u8_Tensor.bin | Bin 0 -> 273600 bytes utilities/test_suite/rpp_test_suite_common.h | 1 + 15 files changed, 1908 insertions(+), 3 deletions(-) create mode 100644 src/modules/cpu/kernel/vignette.hpp create mode 100644 src/modules/hip/kernel/vignette.hpp create mode 100644 utilities/test_suite/REFERENCE_OUTPUT/vignette/vignette_u8_Tensor.bin diff --git a/include/rppt_tensor_effects_augmentations.h b/include/rppt_tensor_effects_augmentations.h index 514326ade..4822a3944 100644 --- a/include/rppt_tensor_effects_augmentations.h +++ b/include/rppt_tensor_effects_augmentations.h @@ -418,6 +418,50 @@ RppStatus rppt_ricap_host(RppPtr_t srcPtr, RpptDescPtr srcDescPtr, RppPtr_t dstP RppStatus rppt_ricap_gpu(RppPtr_t srcPtr, RpptDescPtr srcDescPtr, RppPtr_t dstPtr, RpptDescPtr dstDescPtr, Rpp32u *permutedIndicesTensor, RpptROIPtr roiPtrInputCropRegion, RpptRoiType roiType, rppHandle_t rppHandle); #endif // GPU_SUPPORT +/*! \brief Vignette augmentation on HOST backend for a NCHW/NHWC layout tensor + * \details The Vignette augmentation adds a vignette effect 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.jpg Sample Input + * \image html effects_augmentations_vignette_img150x150.jpg Sample 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] vignetteIntensityTensor intensity values to quantify vignette effect (1D tensor of size batchSize with 0 < vignetteIntensityTensor[n] for each image in batch) + * \param [in] roiTensorSrc 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. + */ +// NOTE: Pixel mismatch of 5% is expected between HIP and HOST Tensor variations due to usage of fastexpavx() instead of exp() in HOST Tensor. +RppStatus rppt_vignette_host(RppPtr_t srcPtr, RpptDescPtr srcDescPtr, RppPtr_t dstPtr, RpptDescPtr dstDescPtr, Rpp32f *vignetteIntensityTensor, RpptROIPtr roiTensorPtrSrc, RpptRoiType roiType, rppHandle_t rppHandle); + +#ifdef GPU_SUPPORT +/*! \brief Vignette augmentation on HIP backend for a NCHW/NHWC layout tensor + * \details The vignette augmentation adds a vignette effect 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.jpg Sample Input + * \image html effects_augmentations_vignette_img150x150.jpg Sample Output + * \param [in] srcPtr source tensor in HIP 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 HIP 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] vignetteIntensityTensor intensity values to quantify vignette effect (1D tensor of size batchSize with 0 < vignetteIntensityTensor[n] for each image in batch) + * \param [in] roiTensorSrc ROI data in HIP 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 HIP handle created with \ref rppCreateWithStreamAndBatchSize() + * \return A \ref RppStatus enumeration. + * \retval RPP_SUCCESS Successful completion. + * \retval RPP_ERROR* Unsuccessful completion. + */ +RppStatus rppt_vignette_gpu(RppPtr_t srcPtr, RpptDescPtr srcDescPtr, RppPtr_t dstPtr, RpptDescPtr dstDescPtr, Rpp32f *vignetteIntensityTensor, RpptROIPtr roiTensorPtrSrc, RpptRoiType roiType, rppHandle_t rppHandle); +#endif // GPU_SUPPORT + + /*! @} */ diff --git a/src/include/cpu/rpp_cpu_common.hpp b/src/include/cpu/rpp_cpu_common.hpp index 1e748cc86..cc9a6ab99 100644 --- a/src/include/cpu/rpp_cpu_common.hpp +++ b/src/include/cpu/rpp_cpu_common.hpp @@ -5996,6 +5996,50 @@ inline void compute_sum_24_host(__m256d *p, __m256d *pSumR, __m256d *pSumG, __m2 pSumB[0] = _mm256_add_pd(_mm256_add_pd(p[4], p[5]), pSumB[0]); //add 8B values and bring it down to 4 } +inline void compute_vignette_48_host(__m256 *p, __m256 &pMultiplier, __m256 &pILocComponent, __m256 &pJLocComponent) +{ + __m256 pGaussianValue; + pGaussianValue = fast_exp_avx(_mm256_mul_ps(_mm256_fmadd_ps(pJLocComponent, pJLocComponent, pILocComponent), pMultiplier)); + p[0] = _mm256_mul_ps(p[0], pGaussianValue); // vignette adjustment + p[2] = _mm256_mul_ps(p[2], pGaussianValue); // vignette adjustment + p[4] = _mm256_mul_ps(p[4], pGaussianValue); // vignette adjustment + pJLocComponent = _mm256_add_ps(pJLocComponent, avx_p8); + pGaussianValue = fast_exp_avx(_mm256_mul_ps(_mm256_fmadd_ps(pJLocComponent, pJLocComponent, pILocComponent), pMultiplier)); + p[1] = _mm256_mul_ps(p[1], pGaussianValue); // vignette adjustment + p[3] = _mm256_mul_ps(p[3], pGaussianValue); // vignette adjustment + p[5] = _mm256_mul_ps(p[5], pGaussianValue); // vignette adjustment + pJLocComponent = _mm256_add_ps(pJLocComponent, avx_p8); +} + +inline void compute_vignette_24_host(__m256 *p, __m256 &pMultiplier, __m256 &pILocComponent, __m256 &pJLocComponent) +{ + __m256 pGaussianValue; + pGaussianValue = fast_exp_avx(_mm256_mul_ps(_mm256_fmadd_ps(pJLocComponent, pJLocComponent, pILocComponent), pMultiplier)); + p[0] = _mm256_mul_ps(p[0], pGaussianValue); // vignette adjustment + p[1] = _mm256_mul_ps(p[1], pGaussianValue); // vignette adjustment + p[2] = _mm256_mul_ps(p[2], pGaussianValue); // vignette adjustment + pJLocComponent = _mm256_add_ps(pJLocComponent, avx_p8); +} + +inline void compute_vignette_16_host(__m256 *p, __m256 &pMultiplier, __m256 &pILocComponent, __m256 &pJLocComponent) +{ + __m256 pGaussianValue; + pGaussianValue = fast_exp_avx(_mm256_mul_ps(_mm256_fmadd_ps(pJLocComponent, pJLocComponent, pILocComponent), pMultiplier)); + p[0] = _mm256_mul_ps(p[0], pGaussianValue); // vignette adjustment + pJLocComponent = _mm256_add_ps(pJLocComponent, avx_p8); + pGaussianValue = fast_exp_avx(_mm256_mul_ps(_mm256_fmadd_ps(pJLocComponent, pJLocComponent, pILocComponent), pMultiplier)); + p[1] = _mm256_mul_ps(p[1], pGaussianValue); // vignette adjustment + pJLocComponent = _mm256_add_ps(pJLocComponent, avx_p8); +} + +inline void compute_vignette_8_host(__m256 *p, __m256 &pMultiplier, __m256 &pILocComponent, __m256 &pJLocComponent) +{ + __m256 pGaussianValue; + pGaussianValue = fast_exp_avx(_mm256_mul_ps(_mm256_fmadd_ps(pJLocComponent, pJLocComponent, pILocComponent), pMultiplier)); + p[0] = _mm256_mul_ps(p[0], pGaussianValue); // vignette adjustment + pJLocComponent = _mm256_add_ps(pJLocComponent, avx_p8); +} + inline void reduce_min_32_host(__m256i *pMin, __m128i *result) { __m128i px[2]; diff --git a/src/include/hip/rpp_hip_common.hpp b/src/include/hip/rpp_hip_common.hpp index d9c0ce02d..d894d2efd 100644 --- a/src/include/hip/rpp_hip_common.hpp +++ b/src/include/hip/rpp_hip_common.hpp @@ -1603,6 +1603,20 @@ __device__ __forceinline__ void rpp_hip_math_floor16(d_float16 *srcPtr_f16, d_fl dstPtr_f16->f1[15] = floorf(srcPtr_f16->f1[15]); } +// d_float8 nearbyintf + +__device__ __forceinline__ void rpp_hip_math_nearbyintf8(d_float8 *srcPtr_f8, d_float8 *dstPtr_f8) +{ + dstPtr_f8->f1[0] = nearbyintf(srcPtr_f8->f1[0]); + dstPtr_f8->f1[1] = nearbyintf(srcPtr_f8->f1[1]); + dstPtr_f8->f1[2] = nearbyintf(srcPtr_f8->f1[2]); + dstPtr_f8->f1[3] = nearbyintf(srcPtr_f8->f1[3]); + dstPtr_f8->f1[4] = nearbyintf(srcPtr_f8->f1[4]); + dstPtr_f8->f1[5] = nearbyintf(srcPtr_f8->f1[5]); + dstPtr_f8->f1[6] = nearbyintf(srcPtr_f8->f1[6]); + dstPtr_f8->f1[7] = nearbyintf(srcPtr_f8->f1[7]); +} + // d_float8 add __device__ __forceinline__ void rpp_hip_math_add8(d_float8 *src1Ptr_f8, d_float8 *src2Ptr_f8, d_float8 *dstPtr_f8) @@ -2499,4 +2513,4 @@ __device__ __forceinline__ void rpp_hip_interpolate24_nearest_neighbor_pkd3(T *s rpp_hip_interpolate3_nearest_neighbor_pkd3(srcPtr, srcStrideH, locPtrSrc_f16->f1[7], locPtrSrc_f16->f1[15], roiPtrSrc_i4, &(dst_f24->f3[7])); } -#endif // RPP_HIP_COMMON_H \ No newline at end of file +#endif // RPP_HIP_COMMON_H diff --git a/src/modules/cpu/host_tensor_effects_augmentations.hpp b/src/modules/cpu/host_tensor_effects_augmentations.hpp index 9bb30e6f0..8d50fa213 100644 --- a/src/modules/cpu/host_tensor_effects_augmentations.hpp +++ b/src/modules/cpu/host_tensor_effects_augmentations.hpp @@ -33,5 +33,6 @@ SOFTWARE. #include "kernel/non_linear_blend.hpp" #include "kernel/water.hpp" #include "kernel/ricap.hpp" +#include "kernel/vignette.hpp" #endif // HOST_TENSOR_EFFECTS_AUGMENTATIONS_HPP diff --git a/src/modules/cpu/kernel/vignette.hpp b/src/modules/cpu/kernel/vignette.hpp new file mode 100644 index 000000000..4043588a9 --- /dev/null +++ b/src/modules/cpu/kernel/vignette.hpp @@ -0,0 +1,1321 @@ +/* +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_simd.hpp" +#include "rpp_cpu_common.hpp" + +RppStatus vignette_u8_u8_host_tensor(Rpp8u *srcPtr, + RpptDescPtr srcDescPtr, + Rpp8u *dstPtr, + RpptDescPtr dstDescPtr, + Rpp32f *vignetteIntensityTensor, + 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); + + Rpp32f intensity = vignetteIntensityTensor[batchCount]; + Rpp32s halfHeight = (Rpp32s) (roi.xywhROI.roiHeight >> 1); + Rpp32s halfWidth = (Rpp32s) (roi.xywhROI.roiWidth >> 1); + Rpp32f radius = std::max(halfHeight, halfWidth); + Rpp32f multiplier = -(0.25f * intensity) / (radius * radius); + + Rpp8u *srcPtrImage, *dstPtrImage; + srcPtrImage = srcPtr + batchCount * srcDescPtr->strides.nStride; + dstPtrImage = dstPtr + batchCount * dstDescPtr->strides.nStride; + + Rpp32u bufferLength = roi.xywhROI.roiWidth; + Rpp32u alignedLength = bufferLength & ~15; + + Rpp8u *srcPtrChannel, *dstPtrChannel; + srcPtrChannel = srcPtrImage + (roi.xywhROI.xy.y * srcDescPtr->strides.hStride) + (roi.xywhROI.xy.x * layoutParams.bufferMultiplier); + dstPtrChannel = dstPtrImage; + + __m256 pMultiplier = _mm256_set1_ps(multiplier); + __m256 pHalfWidth = _mm256_set1_ps(halfWidth); + + // Vignette with fused output-layout toggle (NHWC -> NCHW) + if ((srcDescPtr->c == 3) && (srcDescPtr->layout == RpptLayout::NHWC) && (dstDescPtr->layout == RpptLayout::NCHW)) + { + Rpp8u *srcPtrRow, *dstPtrRowR, *dstPtrRowG, *dstPtrRowB; + srcPtrRow = srcPtrChannel; + dstPtrRowR = dstPtrChannel; + dstPtrRowG = dstPtrRowR + dstDescPtr->strides.cStride; + dstPtrRowB = dstPtrRowG + dstDescPtr->strides.cStride; + + for(int i = 0; i < roi.xywhROI.roiHeight; i++) + { + Rpp8u *srcPtrTemp, *dstPtrTempR, *dstPtrTempG, *dstPtrTempB; + srcPtrTemp = srcPtrRow; + dstPtrTempR = dstPtrRowR; + dstPtrTempG = dstPtrRowG; + dstPtrTempB = dstPtrRowB; + + Rpp32s iLoc = i - halfHeight; + Rpp32f iLocComponent = iLoc * iLoc; + __m256 pILocComponent = _mm256_set1_ps(iLocComponent); + __m256 pJLocComponent = _mm256_sub_ps(avx_pDstLocInit , pHalfWidth); + + int vectorLoopCount = 0; + for (; vectorLoopCount < alignedLength; vectorLoopCount += 16) + { + __m256 p[6]; + rpp_simd_load(rpp_load48_u8pkd3_to_f32pln3_avx, srcPtrTemp, p); // simd loads + compute_vignette_48_host(p, pMultiplier, pILocComponent, pJLocComponent); // vignette adjustment + rpp_simd_store(rpp_store48_f32pln3_to_u8pln3_avx, dstPtrTempR, dstPtrTempG, dstPtrTempB, p); // simd stores + srcPtrTemp += 48; + dstPtrTempR += 16; + dstPtrTempG += 16; + dstPtrTempB += 16; + } + for (; vectorLoopCount < bufferLength; vectorLoopCount++) + { + Rpp32s jLoc = vectorLoopCount - halfWidth; + Rpp32f jLocComponent = jLoc * jLoc; + Rpp32f gaussianValue = std::exp((iLocComponent * multiplier)) * std::exp((jLocComponent * multiplier)); + + *dstPtrTempR++ = (Rpp8u) RPPPIXELCHECK(nearbyintf((Rpp32f)srcPtrTemp[0] * gaussianValue)); + *dstPtrTempG++ = (Rpp8u) RPPPIXELCHECK(nearbyintf((Rpp32f)srcPtrTemp[1] * gaussianValue)); + *dstPtrTempB++ = (Rpp8u) RPPPIXELCHECK(nearbyintf((Rpp32f)srcPtrTemp[2] * gaussianValue)); + + srcPtrTemp += 3; + } + + srcPtrRow += srcDescPtr->strides.hStride; + dstPtrRowR += dstDescPtr->strides.hStride; + dstPtrRowG += dstDescPtr->strides.hStride; + dstPtrRowB += dstDescPtr->strides.hStride; + } + } + + // Vignette with fused output-layout toggle (NCHW -> NHWC) + else if ((srcDescPtr->c == 3) && (srcDescPtr->layout == RpptLayout::NCHW) && (dstDescPtr->layout == RpptLayout::NHWC)) + { + Rpp8u *srcPtrRowR, *srcPtrRowG, *srcPtrRowB, *dstPtrRow; + srcPtrRowR = srcPtrChannel; + srcPtrRowG = srcPtrRowR + srcDescPtr->strides.cStride; + srcPtrRowB = srcPtrRowG + srcDescPtr->strides.cStride; + dstPtrRow = dstPtrChannel; + for(int i = 0; i < roi.xywhROI.roiHeight; i++) + { + Rpp8u *srcPtrTempR, *srcPtrTempG, *srcPtrTempB, *dstPtrTemp; + srcPtrTempR = srcPtrRowR; + srcPtrTempG = srcPtrRowG; + srcPtrTempB = srcPtrRowB; + dstPtrTemp = dstPtrRow; + + Rpp32s iLoc = i - halfHeight; + Rpp32f iLocComponent = iLoc * iLoc; + __m256 pILocComponent = _mm256_set1_ps(iLocComponent); + __m256 pJLocComponent = _mm256_sub_ps(avx_pDstLocInit , pHalfWidth); + + int vectorLoopCount = 0; + for (; vectorLoopCount < alignedLength; vectorLoopCount += 16) + { + __m256 p[6]; + rpp_simd_load(rpp_load48_u8pln3_to_f32pln3_avx, srcPtrTempR, srcPtrTempG, srcPtrTempB, p); // simd loads + compute_vignette_48_host(p, pMultiplier, pILocComponent, pJLocComponent); // vignette adjustment + rpp_simd_store(rpp_store48_f32pln3_to_u8pkd3_avx, dstPtrTemp, p); // simd stores + srcPtrTempR += 16; + srcPtrTempG += 16; + srcPtrTempB += 16; + dstPtrTemp += 48; + } + for (; vectorLoopCount < bufferLength; vectorLoopCount++) + { + Rpp32s jLoc = vectorLoopCount - halfWidth; + Rpp32f jLocComponent = jLoc * jLoc; + Rpp32f gaussianValue = std::exp((iLocComponent * multiplier)) * std::exp((jLocComponent * multiplier)); + + dstPtrTemp[0] = (Rpp8u) RPPPIXELCHECK(nearbyintf((Rpp32f)*srcPtrTempR * gaussianValue)); + dstPtrTemp[1] = (Rpp8u) RPPPIXELCHECK(nearbyintf((Rpp32f)*srcPtrTempG * gaussianValue)); + dstPtrTemp[2] = (Rpp8u) RPPPIXELCHECK(nearbyintf((Rpp32f)*srcPtrTempB * gaussianValue)); + + srcPtrTempR++; + srcPtrTempG++; + srcPtrTempB++; + dstPtrTemp += 3; + } + + srcPtrRowR += srcDescPtr->strides.hStride; + srcPtrRowG += srcDescPtr->strides.hStride; + srcPtrRowB += srcDescPtr->strides.hStride; + dstPtrRow += dstDescPtr->strides.hStride; + } + } + + // Vignette with fused output-layout toggle (NHWC -> NHWC) + else if ((srcDescPtr->c == 3) && (srcDescPtr->layout == RpptLayout::NHWC) && (dstDescPtr->layout == RpptLayout::NHWC)) + { + Rpp8u *srcPtrRow, *dstPtrRow; + srcPtrRow = srcPtrChannel; + dstPtrRow = dstPtrChannel; + for(int i = 0; i < roi.xywhROI.roiHeight; i++) + { + Rpp8u *srcPtrTemp, *dstPtrTemp; + srcPtrTemp = srcPtrRow; + dstPtrTemp = dstPtrRow; + + Rpp32s iLoc = i - halfHeight; + Rpp32f iLocComponent = iLoc * iLoc; + __m256 pILocComponent = _mm256_set1_ps(iLocComponent); + __m256 pJLocComponent = _mm256_sub_ps(avx_pDstLocInit , pHalfWidth); + + int vectorLoopCount = 0; + for (; vectorLoopCount < alignedLength; vectorLoopCount += 16) + { + __m256 p[6]; + rpp_simd_load(rpp_load48_u8pkd3_to_f32pln3_avx, srcPtrTemp, p); // simd loads + compute_vignette_48_host(p, pMultiplier, pILocComponent, pJLocComponent); // vignette adjustment + rpp_simd_store(rpp_store48_f32pln3_to_u8pkd3_avx, dstPtrTemp, p); // simd stores + srcPtrTemp += 48; + dstPtrTemp += 48; + } + for (; vectorLoopCount < bufferLength; vectorLoopCount++) + { + Rpp32s jLoc = vectorLoopCount - halfWidth; + Rpp32f jLocComponent = jLoc * jLoc; + Rpp32f gaussianValue = std::exp((iLocComponent * multiplier)) * std::exp((jLocComponent * multiplier)); + + dstPtrTemp[0] = (Rpp8u) RPPPIXELCHECK(nearbyintf((Rpp32f)srcPtrTemp[0] * gaussianValue)); + dstPtrTemp[1] = (Rpp8u) RPPPIXELCHECK(nearbyintf((Rpp32f)srcPtrTemp[1] * gaussianValue)); + dstPtrTemp[2] = (Rpp8u) RPPPIXELCHECK(nearbyintf((Rpp32f)srcPtrTemp[2] * gaussianValue)); + + srcPtrTemp += 3; + dstPtrTemp += 3; + } + + srcPtrRow += srcDescPtr->strides.hStride; + dstPtrRow += dstDescPtr->strides.hStride; + } + } + + // Vignette with fused output-layout toggle (NCHW -> NCHW) + else if ((srcDescPtr->c == 3) && (srcDescPtr->layout == RpptLayout::NCHW) && (dstDescPtr->layout == RpptLayout::NCHW)) + { + Rpp8u *srcPtrRowR, *srcPtrRowG, *srcPtrRowB, *dstPtrRowR, *dstPtrRowG, *dstPtrRowB; + srcPtrRowR = srcPtrChannel; + srcPtrRowG = srcPtrRowR + srcDescPtr->strides.cStride; + srcPtrRowB = srcPtrRowG + srcDescPtr->strides.cStride; + dstPtrRowR = dstPtrChannel; + dstPtrRowG = dstPtrRowR + dstDescPtr->strides.cStride; + dstPtrRowB = dstPtrRowG + dstDescPtr->strides.cStride; + + for(int i = 0; i < roi.xywhROI.roiHeight; i++) + { + Rpp8u *srcPtrTempR, *srcPtrTempG, *srcPtrTempB, *dstPtrTempR, *dstPtrTempG, *dstPtrTempB; + srcPtrTempR = srcPtrRowR; + srcPtrTempG = srcPtrRowG; + srcPtrTempB = srcPtrRowB; + dstPtrTempR = dstPtrRowR; + dstPtrTempG = dstPtrRowG; + dstPtrTempB = dstPtrRowB; + + Rpp32s iLoc = i - halfHeight; + Rpp32f iLocComponent = iLoc * iLoc; + __m256 pILocComponent = _mm256_set1_ps(iLocComponent); + __m256 pJLocComponent = _mm256_sub_ps(avx_pDstLocInit , pHalfWidth); + + int vectorLoopCount = 0; + for (; vectorLoopCount < alignedLength; vectorLoopCount += 16) + { + __m256 p[6]; + rpp_simd_load(rpp_load48_u8pln3_to_f32pln3_avx, srcPtrTempR, srcPtrTempG, srcPtrTempB, p); // simd loads + compute_vignette_48_host(p, pMultiplier, pILocComponent, pJLocComponent); // vignette adjustment + rpp_simd_store(rpp_store48_f32pln3_to_u8pln3_avx, dstPtrTempR, dstPtrTempG, dstPtrTempB, p); // simd stores + srcPtrTempR += 16; + srcPtrTempG += 16; + srcPtrTempB += 16; + dstPtrTempR += 16; + dstPtrTempG += 16; + dstPtrTempB += 16; + } + for (; vectorLoopCount < bufferLength; vectorLoopCount++) + { + Rpp32s jLoc = vectorLoopCount - halfWidth; + Rpp32f jLocComponent = jLoc * jLoc; + Rpp32f gaussianValue = std::exp((iLocComponent * multiplier)) * std::exp((jLocComponent * multiplier)); + + *dstPtrTempR++ = (Rpp8u) RPPPIXELCHECK(nearbyintf((Rpp32f)*srcPtrTempR * gaussianValue)); + *dstPtrTempG++ = (Rpp8u) RPPPIXELCHECK(nearbyintf((Rpp32f)*srcPtrTempG * gaussianValue)); + *dstPtrTempB++ = (Rpp8u) RPPPIXELCHECK(nearbyintf((Rpp32f)*srcPtrTempB * gaussianValue)); + + srcPtrTempR++; + srcPtrTempG++; + srcPtrTempB++; + } + + srcPtrRowR += srcDescPtr->strides.hStride; + srcPtrRowG += srcDescPtr->strides.hStride; + srcPtrRowB += srcDescPtr->strides.hStride; + dstPtrRowR += dstDescPtr->strides.hStride; + dstPtrRowG += dstDescPtr->strides.hStride; + dstPtrRowB += dstDescPtr->strides.hStride; + } + } + + // Vignette without fused output-layout toggle single channel (NCHW -> NCHW) + else if ((srcDescPtr->c == 1) && (srcDescPtr->layout == RpptLayout::NCHW) && (dstDescPtr->layout == RpptLayout::NCHW)) + { + Rpp8u *srcPtrRow, *dstPtrRow; + srcPtrRow = srcPtrChannel; + dstPtrRow = dstPtrChannel; + for(int i = 0; i < roi.xywhROI.roiHeight; i++) + { + Rpp8u *srcPtrTemp, *dstPtrTemp; + srcPtrTemp = srcPtrRow; + dstPtrTemp = dstPtrRow; + + Rpp32s iLoc = i - halfHeight; + Rpp32f iLocComponent = iLoc * iLoc; + __m256 pILocComponent = _mm256_set1_ps(iLocComponent); + __m256 pJLocComponent = _mm256_sub_ps(avx_pDstLocInit , pHalfWidth); + + int vectorLoopCount = 0; + for (; vectorLoopCount < alignedLength; vectorLoopCount += 16) + { + __m256 p[2]; + rpp_simd_load(rpp_load16_u8_to_f32_avx, srcPtrTemp, p); // simd loads + compute_vignette_16_host(p, pMultiplier, pILocComponent, pJLocComponent); // vignette adjustment + rpp_simd_store(rpp_store16_f32_to_u8_avx, dstPtrTemp, p); // simd stores + srcPtrTemp += 16; + dstPtrTemp += 16; + } + for (; vectorLoopCount < bufferLength; vectorLoopCount++) + { + Rpp32s jLoc = vectorLoopCount - halfWidth; + Rpp32f jLocComponent = jLoc * jLoc; + Rpp32f gaussianValue = std::exp((iLocComponent * multiplier)) * std::exp((jLocComponent * multiplier)); + *dstPtrTemp++ = (Rpp8u) RPPPIXELCHECK(nearbyintf((Rpp32f)*srcPtrTemp * gaussianValue)); + srcPtrTemp++; + } + + srcPtrRow += srcDescPtr->strides.hStride; + dstPtrRow += dstDescPtr->strides.hStride; + } + } + } + + return RPP_SUCCESS; +} + +RppStatus vignette_f32_f32_host_tensor(Rpp32f *srcPtr, + RpptDescPtr srcDescPtr, + Rpp32f *dstPtr, + RpptDescPtr dstDescPtr, + Rpp32f *vignetteIntensityTensor, + 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); + + Rpp32f intensity = vignetteIntensityTensor[batchCount]; + Rpp32s halfHeight = (Rpp32s) (roi.xywhROI.roiHeight >> 1); + Rpp32s halfWidth = (Rpp32s) (roi.xywhROI.roiWidth >> 1); + Rpp32f radius = std::max(halfHeight, halfWidth); + Rpp32f multiplier = -(0.25f * intensity) / (radius * radius); + + Rpp32f *srcPtrImage, *dstPtrImage; + srcPtrImage = srcPtr + batchCount * srcDescPtr->strides.nStride; + dstPtrImage = dstPtr + batchCount * dstDescPtr->strides.nStride; + + Rpp32u bufferLength = roi.xywhROI.roiWidth; + Rpp32u alignedLength = bufferLength & ~7; + + Rpp32f *srcPtrChannel, *dstPtrChannel; + srcPtrChannel = srcPtrImage + (roi.xywhROI.xy.y * srcDescPtr->strides.hStride) + (roi.xywhROI.xy.x * layoutParams.bufferMultiplier); + dstPtrChannel = dstPtrImage; + + __m256 pMultiplier = _mm256_set1_ps(multiplier); + __m256 pHalfWidth = _mm256_set1_ps(halfWidth); + + // Vignette with fused output-layout toggle (NHWC -> NCHW) + if ((srcDescPtr->c == 3) && (srcDescPtr->layout == RpptLayout::NHWC) && (dstDescPtr->layout == RpptLayout::NCHW)) + { + Rpp32f *srcPtrRow, *dstPtrRowR, *dstPtrRowG, *dstPtrRowB; + srcPtrRow = srcPtrChannel; + dstPtrRowR = dstPtrChannel; + dstPtrRowG = dstPtrRowR + dstDescPtr->strides.cStride; + dstPtrRowB = dstPtrRowG + dstDescPtr->strides.cStride; + + for(int i = 0; i < roi.xywhROI.roiHeight; i++) + { + Rpp32f *srcPtrTemp, *dstPtrTempR, *dstPtrTempG, *dstPtrTempB; + srcPtrTemp = srcPtrRow; + dstPtrTempR = dstPtrRowR; + dstPtrTempG = dstPtrRowG; + dstPtrTempB = dstPtrRowB; + + Rpp32s iLoc = i - halfHeight; + Rpp32f iLocComponent = iLoc * iLoc; + __m256 pILocComponent = _mm256_set1_ps(iLocComponent); + __m256 pJLocComponent = _mm256_sub_ps(avx_pDstLocInit , pHalfWidth); + + int vectorLoopCount = 0; + for (; vectorLoopCount < alignedLength; vectorLoopCount += 8) + { + __m256 p[3]; + rpp_simd_load(rpp_load24_f32pkd3_to_f32pln3_avx, srcPtrTemp, p); // simd loads // simd loads + compute_vignette_24_host(p, pMultiplier, pILocComponent, pJLocComponent); // vignette adjustment + rpp_simd_store(rpp_store24_f32pln3_to_f32pln3_avx, dstPtrTempR, dstPtrTempG, dstPtrTempB, p); // simd stores + srcPtrTemp += 24; + dstPtrTempR += 8; + dstPtrTempG += 8; + dstPtrTempB += 8; + } + for (; vectorLoopCount < bufferLength; vectorLoopCount++) + { + Rpp32s jLoc = vectorLoopCount - halfWidth; + Rpp32f jLocComponent = jLoc * jLoc; + Rpp32f gaussianValue = std::exp((iLocComponent * multiplier)) * std::exp((jLocComponent * multiplier)); + + *dstPtrTempR++ = RPPPIXELCHECKF32(srcPtrTemp[0] * gaussianValue); + *dstPtrTempG++ = RPPPIXELCHECKF32(srcPtrTemp[1] * gaussianValue); + *dstPtrTempB++ = RPPPIXELCHECKF32(srcPtrTemp[2] * gaussianValue); + + srcPtrTemp += 3; + } + + srcPtrRow += srcDescPtr->strides.hStride; + dstPtrRowR += dstDescPtr->strides.hStride; + dstPtrRowG += dstDescPtr->strides.hStride; + dstPtrRowB += dstDescPtr->strides.hStride; + } + } + + // Vignette with fused output-layout toggle (NCHW -> NHWC) + else if ((srcDescPtr->c == 3) && (srcDescPtr->layout == RpptLayout::NCHW) && (dstDescPtr->layout == RpptLayout::NHWC)) + { + Rpp32f *srcPtrRowR, *srcPtrRowG, *srcPtrRowB, *dstPtrRow; + srcPtrRowR = srcPtrChannel; + srcPtrRowG = srcPtrRowR + srcDescPtr->strides.cStride; + srcPtrRowB = srcPtrRowG + srcDescPtr->strides.cStride; + dstPtrRow = dstPtrChannel; + for(int i = 0; i < roi.xywhROI.roiHeight; i++) + { + Rpp32f *srcPtrTempR, *srcPtrTempG, *srcPtrTempB, *dstPtrTemp; + srcPtrTempR = srcPtrRowR; + srcPtrTempG = srcPtrRowG; + srcPtrTempB = srcPtrRowB; + dstPtrTemp = dstPtrRow; + + Rpp32s iLoc = i - halfHeight; + Rpp32f iLocComponent = iLoc * iLoc; + __m256 pILocComponent = _mm256_set1_ps(iLocComponent); + __m256 pJLocComponent = _mm256_sub_ps(avx_pDstLocInit , pHalfWidth); + + int vectorLoopCount = 0; + for (; vectorLoopCount < alignedLength; vectorLoopCount += 8) + { + __m256 p[3]; + rpp_simd_load(rpp_load24_f32pln3_to_f32pln3_avx, srcPtrTempR, srcPtrTempG, srcPtrTempB, p); // simd loads + compute_vignette_24_host(p, pMultiplier, pILocComponent, pJLocComponent); // vignette adjustment + rpp_simd_store(rpp_store24_f32pln3_to_f32pkd3_avx, dstPtrTemp, p); // simd stores + srcPtrTempR += 8; + srcPtrTempG += 8; + srcPtrTempB += 8; + dstPtrTemp += 24; + } + for (; vectorLoopCount < bufferLength; vectorLoopCount++) + { + Rpp32s jLoc = vectorLoopCount - halfWidth; + Rpp32f jLocComponent = jLoc * jLoc; + Rpp32f gaussianValue = std::exp((iLocComponent * multiplier)) * std::exp((jLocComponent * multiplier)); + + dstPtrTemp[0] = RPPPIXELCHECKF32(*srcPtrTempR * gaussianValue); + dstPtrTemp[1] = RPPPIXELCHECKF32(*srcPtrTempG * gaussianValue); + dstPtrTemp[2] = RPPPIXELCHECKF32(*srcPtrTempB * gaussianValue); + + srcPtrTempR++; + srcPtrTempG++; + srcPtrTempB++; + dstPtrTemp += 3; + } + + srcPtrRowR += srcDescPtr->strides.hStride; + srcPtrRowG += srcDescPtr->strides.hStride; + srcPtrRowB += srcDescPtr->strides.hStride; + dstPtrRow += dstDescPtr->strides.hStride; + } + } + + // Vignette with fused output-layout toggle (NHWC -> NHWC) + else if ((srcDescPtr->c == 3) && (srcDescPtr->layout == RpptLayout::NHWC) && (dstDescPtr->layout == RpptLayout::NHWC)) + { + Rpp32f *srcPtrRow, *dstPtrRow; + srcPtrRow = srcPtrChannel; + dstPtrRow = dstPtrChannel; + for(int i = 0; i < roi.xywhROI.roiHeight; i++) + { + Rpp32f *srcPtrTemp, *dstPtrTemp; + srcPtrTemp = srcPtrRow; + dstPtrTemp = dstPtrRow; + + Rpp32s iLoc = i - halfHeight; + Rpp32f iLocComponent = iLoc * iLoc; + __m256 pILocComponent = _mm256_set1_ps(iLocComponent); + __m256 pJLocComponent = _mm256_sub_ps(avx_pDstLocInit , pHalfWidth); + + int vectorLoopCount = 0; + for (; vectorLoopCount < alignedLength; vectorLoopCount += 8) + { + __m256 p[3]; + rpp_simd_load(rpp_load24_f32pkd3_to_f32pln3_avx, srcPtrTemp, p); // simd loads + compute_vignette_24_host(p, pMultiplier, pILocComponent, pJLocComponent); // vignette adjustment + rpp_simd_store(rpp_store24_f32pln3_to_f32pkd3_avx, dstPtrTemp, p); // simd stores + srcPtrTemp += 24; + dstPtrTemp += 24; + } + for (; vectorLoopCount < bufferLength; vectorLoopCount++) + { + Rpp32s jLoc = vectorLoopCount - halfWidth; + Rpp32f jLocComponent = jLoc * jLoc; + Rpp32f gaussianValue = std::exp((iLocComponent * multiplier)) * std::exp((jLocComponent * multiplier)); + + dstPtrTemp[0] = RPPPIXELCHECKF32(srcPtrTemp[0] * gaussianValue); + dstPtrTemp[1] = RPPPIXELCHECKF32(srcPtrTemp[1] * gaussianValue); + dstPtrTemp[2] = RPPPIXELCHECKF32(srcPtrTemp[2] * gaussianValue); + + srcPtrTemp += 3; + dstPtrTemp += 3; + } + + srcPtrRow += srcDescPtr->strides.hStride; + dstPtrRow += dstDescPtr->strides.hStride; + } + } + + // Vignette with fused output-layout toggle (NCHW -> NCHW) + else if ((srcDescPtr->c == 3) && (srcDescPtr->layout == RpptLayout::NCHW) && (dstDescPtr->layout == RpptLayout::NCHW)) + { + Rpp32f *srcPtrRowR, *srcPtrRowG, *srcPtrRowB, *dstPtrRowR, *dstPtrRowG, *dstPtrRowB; + srcPtrRowR = srcPtrChannel; + srcPtrRowG = srcPtrRowR + srcDescPtr->strides.cStride; + srcPtrRowB = srcPtrRowG + srcDescPtr->strides.cStride; + dstPtrRowR = dstPtrChannel; + dstPtrRowG = dstPtrRowR + dstDescPtr->strides.cStride; + dstPtrRowB = dstPtrRowG + dstDescPtr->strides.cStride; + + for(int i = 0; i < roi.xywhROI.roiHeight; i++) + { + Rpp32f *srcPtrTempR, *srcPtrTempG, *srcPtrTempB, *dstPtrTempR, *dstPtrTempG, *dstPtrTempB; + srcPtrTempR = srcPtrRowR; + srcPtrTempG = srcPtrRowG; + srcPtrTempB = srcPtrRowB; + dstPtrTempR = dstPtrRowR; + dstPtrTempG = dstPtrRowG; + dstPtrTempB = dstPtrRowB; + + Rpp32s iLoc = i - halfHeight; + Rpp32f iLocComponent = iLoc * iLoc; + __m256 pILocComponent = _mm256_set1_ps(iLocComponent); + __m256 pJLocComponent = _mm256_sub_ps(avx_pDstLocInit , pHalfWidth); + + int vectorLoopCount = 0; + for (; vectorLoopCount < alignedLength; vectorLoopCount += 8) + { + __m256 p[6]; + rpp_simd_load(rpp_load24_f32pln3_to_f32pln3_avx, srcPtrTempR, srcPtrTempG, srcPtrTempB, p); // simd loads + compute_vignette_24_host(p, pMultiplier, pILocComponent, pJLocComponent); // vignette adjustment + rpp_simd_store(rpp_store24_f32pln3_to_f32pln3_avx, dstPtrTempR, dstPtrTempG, dstPtrTempB, p); // simd stores + srcPtrTempR += 8; + srcPtrTempG += 8; + srcPtrTempB += 8; + dstPtrTempR += 8; + dstPtrTempG += 8; + dstPtrTempB += 8; + } + for (; vectorLoopCount < bufferLength; vectorLoopCount++) + { + Rpp32s jLoc = vectorLoopCount - halfWidth; + Rpp32f jLocComponent = jLoc * jLoc; + Rpp32f gaussianValue = std::exp((iLocComponent * multiplier)) * std::exp((jLocComponent * multiplier)); + + *dstPtrTempR++ = RPPPIXELCHECKF32(*srcPtrTempR * gaussianValue); + *dstPtrTempG++ = RPPPIXELCHECKF32(*srcPtrTempG * gaussianValue); + *dstPtrTempB++ = RPPPIXELCHECKF32(*srcPtrTempB * gaussianValue); + + srcPtrTempR++; + srcPtrTempG++; + srcPtrTempB++; + } + + srcPtrRowR += srcDescPtr->strides.hStride; + srcPtrRowG += srcDescPtr->strides.hStride; + srcPtrRowB += srcDescPtr->strides.hStride; + dstPtrRowR += dstDescPtr->strides.hStride; + dstPtrRowG += dstDescPtr->strides.hStride; + dstPtrRowB += dstDescPtr->strides.hStride; + } + } + + // Vignette without fused output-layout toggle single channel (NCHW -> NCHW) + else if ((srcDescPtr->c == 1) && (srcDescPtr->layout == RpptLayout::NCHW) && (dstDescPtr->layout == RpptLayout::NCHW)) + { + Rpp32f *srcPtrRow, *dstPtrRow; + srcPtrRow = srcPtrChannel; + dstPtrRow = dstPtrChannel; + for(int i = 0; i < roi.xywhROI.roiHeight; i++) + { + Rpp32f *srcPtrTemp, *dstPtrTemp; + srcPtrTemp = srcPtrRow; + dstPtrTemp = dstPtrRow; + + Rpp32s iLoc = i - halfHeight; + Rpp32f iLocComponent = iLoc * iLoc; + __m256 pILocComponent = _mm256_set1_ps(iLocComponent); + __m256 pJLocComponent = _mm256_sub_ps(avx_pDstLocInit , pHalfWidth); + + int vectorLoopCount = 0; + for (; vectorLoopCount < alignedLength; vectorLoopCount += 8) + { + __m256 p[2]; + rpp_simd_load(rpp_load8_f32_to_f32_avx, srcPtrTemp, p); // simd loads + compute_vignette_8_host(p, pMultiplier, pILocComponent, pJLocComponent); // vignette adjustment + rpp_simd_store(rpp_store8_f32_to_f32_avx, dstPtrTemp, p); // simd stores + srcPtrTemp += 8; + dstPtrTemp += 8; + } + for (; vectorLoopCount < bufferLength; vectorLoopCount++) + { + Rpp32s jLoc = vectorLoopCount - halfWidth; + Rpp32f jLocComponent = jLoc * jLoc; + Rpp32f gaussianValue = std::exp((iLocComponent * multiplier)) * std::exp((jLocComponent * multiplier)); + *dstPtrTemp++ = RPPPIXELCHECKF32(*srcPtrTemp * gaussianValue); + srcPtrTemp++; + } + + srcPtrRow += srcDescPtr->strides.hStride; + dstPtrRow += dstDescPtr->strides.hStride; + } + } + } + + return RPP_SUCCESS; +} + +RppStatus vignette_i8_i8_host_tensor(Rpp8s *srcPtr, + RpptDescPtr srcDescPtr, + Rpp8s *dstPtr, + RpptDescPtr dstDescPtr, + Rpp32f *vignetteIntensityTensor, + 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); + + Rpp32f intensity = vignetteIntensityTensor[batchCount]; + Rpp32s halfHeight = (Rpp32s) (roi.xywhROI.roiHeight >> 1); + Rpp32s halfWidth = (Rpp32s) (roi.xywhROI.roiWidth >> 1); + Rpp32f radius = std::max(halfHeight, halfWidth); + Rpp32f multiplier = -(0.25f * intensity) / (radius * radius); + + Rpp8s *srcPtrImage, *dstPtrImage; + srcPtrImage = srcPtr + batchCount * srcDescPtr->strides.nStride; + dstPtrImage = dstPtr + batchCount * dstDescPtr->strides.nStride; + + Rpp32u bufferLength = roi.xywhROI.roiWidth; + Rpp32u alignedLength = bufferLength & ~15; + + Rpp8s *srcPtrChannel, *dstPtrChannel; + srcPtrChannel = srcPtrImage + (roi.xywhROI.xy.y * srcDescPtr->strides.hStride) + (roi.xywhROI.xy.x * layoutParams.bufferMultiplier); + dstPtrChannel = dstPtrImage; + + __m256 pMultiplier = _mm256_set1_ps(multiplier); + __m256 pHalfWidth = _mm256_set1_ps(halfWidth); + + // Vignette with fused output-layout toggle (NHWC -> NCHW) + if ((srcDescPtr->c == 3) && (srcDescPtr->layout == RpptLayout::NHWC) && (dstDescPtr->layout == RpptLayout::NCHW)) + { + Rpp8s *srcPtrRow, *dstPtrRowR, *dstPtrRowG, *dstPtrRowB; + srcPtrRow = srcPtrChannel; + dstPtrRowR = dstPtrChannel; + dstPtrRowG = dstPtrRowR + dstDescPtr->strides.cStride; + dstPtrRowB = dstPtrRowG + dstDescPtr->strides.cStride; + + for(int i = 0; i < roi.xywhROI.roiHeight; i++) + { + Rpp8s *srcPtrTemp, *dstPtrTempR, *dstPtrTempG, *dstPtrTempB; + srcPtrTemp = srcPtrRow; + dstPtrTempR = dstPtrRowR; + dstPtrTempG = dstPtrRowG; + dstPtrTempB = dstPtrRowB; + + Rpp32s iLoc = i - halfHeight; + Rpp32f iLocComponent = iLoc * iLoc; + __m256 pILocComponent = _mm256_set1_ps(iLocComponent); + __m256 pJLocComponent = _mm256_sub_ps(avx_pDstLocInit , pHalfWidth); + + int vectorLoopCount = 0; + for (; vectorLoopCount < alignedLength; vectorLoopCount += 16) + { + __m256 p[6]; + rpp_simd_load(rpp_load48_i8pkd3_to_f32pln3_avx, srcPtrTemp, p); // simd loads + compute_vignette_48_host(p, pMultiplier, pILocComponent, pJLocComponent); // vignette adjustment + rpp_simd_store(rpp_store48_f32pln3_to_i8pln3_avx, dstPtrTempR, dstPtrTempG, dstPtrTempB, p); // simd stores + srcPtrTemp += 48; + dstPtrTempR += 16; + dstPtrTempG += 16; + dstPtrTempB += 16; + } + for (; vectorLoopCount < bufferLength; vectorLoopCount++) + { + Rpp32s jLoc = vectorLoopCount - halfWidth; + Rpp32f jLocComponent = jLoc * jLoc; + Rpp32f gaussianValue = std::exp((iLocComponent * multiplier)) * std::exp((jLocComponent * multiplier)); + Rpp32f srcPtrTempI8[3]; + srcPtrTempI8[0] = (Rpp32f)srcPtrTemp[0] + 128; + srcPtrTempI8[1] = (Rpp32f)srcPtrTemp[1] + 128; + srcPtrTempI8[2] = (Rpp32f)srcPtrTemp[2] + 128; + + *dstPtrTempR++ = (Rpp8s) RPPPIXELCHECKI8(nearbyintf(srcPtrTempI8[0] * gaussianValue) - 128); + *dstPtrTempG++ = (Rpp8s) RPPPIXELCHECKI8(nearbyintf(srcPtrTempI8[1] * gaussianValue) - 128); + *dstPtrTempB++ = (Rpp8s) RPPPIXELCHECKI8(nearbyintf(srcPtrTempI8[2] * gaussianValue) - 128); + + srcPtrTemp += 3; + } + + srcPtrRow += srcDescPtr->strides.hStride; + dstPtrRowR += dstDescPtr->strides.hStride; + dstPtrRowG += dstDescPtr->strides.hStride; + dstPtrRowB += dstDescPtr->strides.hStride; + } + } + + // Vignette with fused output-layout toggle (NCHW -> NHWC) + else if ((srcDescPtr->c == 3) && (srcDescPtr->layout == RpptLayout::NCHW) && (dstDescPtr->layout == RpptLayout::NHWC)) + { + Rpp8s *srcPtrRowR, *srcPtrRowG, *srcPtrRowB, *dstPtrRow; + srcPtrRowR = srcPtrChannel; + srcPtrRowG = srcPtrRowR + srcDescPtr->strides.cStride; + srcPtrRowB = srcPtrRowG + srcDescPtr->strides.cStride; + dstPtrRow = dstPtrChannel; + for(int i = 0; i < roi.xywhROI.roiHeight; i++) + { + Rpp8s *srcPtrTempR, *srcPtrTempG, *srcPtrTempB, *dstPtrTemp; + srcPtrTempR = srcPtrRowR; + srcPtrTempG = srcPtrRowG; + srcPtrTempB = srcPtrRowB; + dstPtrTemp = dstPtrRow; + + Rpp32s iLoc = i - halfHeight; + Rpp32f iLocComponent = iLoc * iLoc; + __m256 pILocComponent = _mm256_set1_ps(iLocComponent); + __m256 pJLocComponent = _mm256_sub_ps(avx_pDstLocInit , pHalfWidth); + + int vectorLoopCount = 0; + for (; vectorLoopCount < alignedLength; vectorLoopCount += 16) + { + __m256 p[6]; + rpp_simd_load(rpp_load48_i8pln3_to_f32pln3_avx, srcPtrTempR, srcPtrTempG, srcPtrTempB, p); // simd loads + compute_vignette_48_host(p, pMultiplier, pILocComponent, pJLocComponent); // vignette adjustment + rpp_simd_store(rpp_store48_f32pln3_to_i8pkd3_avx, dstPtrTemp, p); // simd stores + srcPtrTempR += 16; + srcPtrTempG += 16; + srcPtrTempB += 16; + dstPtrTemp += 48; + } + for (; vectorLoopCount < bufferLength; vectorLoopCount++) + { + Rpp32s jLoc = vectorLoopCount - halfWidth; + Rpp32f jLocComponent = jLoc * jLoc; + Rpp32f gaussianValue = std::exp((iLocComponent * multiplier)) * std::exp((jLocComponent * multiplier)); + Rpp32f srcPtrTempI8[3]; + srcPtrTempI8[0] = (Rpp32f)*srcPtrTempR + 128; + srcPtrTempI8[1] = (Rpp32f)*srcPtrTempG + 128; + srcPtrTempI8[2] = (Rpp32f)*srcPtrTempB + 128; + + dstPtrTemp[0] = (Rpp8s) RPPPIXELCHECKI8(nearbyintf(srcPtrTempI8[0] * gaussianValue) - 128); + dstPtrTemp[1] = (Rpp8s) RPPPIXELCHECKI8(nearbyintf(srcPtrTempI8[1] * gaussianValue) - 128); + dstPtrTemp[2] = (Rpp8s) RPPPIXELCHECKI8(nearbyintf(srcPtrTempI8[2] * gaussianValue) - 128); + + srcPtrTempR++; + srcPtrTempG++; + srcPtrTempB++; + dstPtrTemp += 3; + } + + srcPtrRowR += srcDescPtr->strides.hStride; + srcPtrRowG += srcDescPtr->strides.hStride; + srcPtrRowB += srcDescPtr->strides.hStride; + dstPtrRow += dstDescPtr->strides.hStride; + } + } + + // Vignette with fused output-layout toggle (NHWC -> NHWC) + else if ((srcDescPtr->c == 3) && (srcDescPtr->layout == RpptLayout::NHWC) && (dstDescPtr->layout == RpptLayout::NHWC)) + { + Rpp8s *srcPtrRow, *dstPtrRow; + srcPtrRow = srcPtrChannel; + dstPtrRow = dstPtrChannel; + for(int i = 0; i < roi.xywhROI.roiHeight; i++) + { + Rpp8s *srcPtrTemp, *dstPtrTemp; + srcPtrTemp = srcPtrRow; + dstPtrTemp = dstPtrRow; + + Rpp32s iLoc = i - halfHeight; + Rpp32f iLocComponent = iLoc * iLoc; + __m256 pILocComponent = _mm256_set1_ps(iLocComponent); + __m256 pJLocComponent = _mm256_sub_ps(avx_pDstLocInit , pHalfWidth); + + int vectorLoopCount = 0; + for (; vectorLoopCount < alignedLength; vectorLoopCount += 16) + { + __m256 p[6]; + rpp_simd_load(rpp_load48_i8pkd3_to_f32pln3_avx, srcPtrTemp, p); // simd loads + compute_vignette_48_host(p, pMultiplier, pILocComponent, pJLocComponent); // vignette adjustment + rpp_simd_store(rpp_store48_f32pln3_to_i8pkd3_avx, dstPtrTemp, p); // simd stores + srcPtrTemp += 48; + dstPtrTemp += 48; + } + for (; vectorLoopCount < bufferLength; vectorLoopCount++) + { + Rpp32s jLoc = vectorLoopCount - halfWidth; + Rpp32f jLocComponent = jLoc * jLoc; + Rpp32f gaussianValue = std::exp((iLocComponent * multiplier)) * std::exp((jLocComponent * multiplier)); + Rpp32f srcPtrTempI8[3]; + srcPtrTempI8[0] = (Rpp32f)srcPtrTemp[0] + 128; + srcPtrTempI8[1] = (Rpp32f)srcPtrTemp[1] + 128; + srcPtrTempI8[2] = (Rpp32f)srcPtrTemp[2] + 128; + + dstPtrTemp[0] = (Rpp8s) RPPPIXELCHECKI8(nearbyintf(srcPtrTempI8[0] * gaussianValue) - 128); + dstPtrTemp[1] = (Rpp8s) RPPPIXELCHECKI8(nearbyintf(srcPtrTempI8[1] * gaussianValue) - 128); + dstPtrTemp[2] = (Rpp8s) RPPPIXELCHECKI8(nearbyintf(srcPtrTempI8[2] * gaussianValue) - 128); + + srcPtrTemp += 3; + dstPtrTemp += 3; + } + + srcPtrRow += srcDescPtr->strides.hStride; + dstPtrRow += dstDescPtr->strides.hStride; + } + } + + // Vignette with fused output-layout toggle (NCHW -> NCHW) + else if ((srcDescPtr->c == 3) && (srcDescPtr->layout == RpptLayout::NCHW) && (dstDescPtr->layout == RpptLayout::NCHW)) + { + Rpp8s *srcPtrRowR, *srcPtrRowG, *srcPtrRowB, *dstPtrRowR, *dstPtrRowG, *dstPtrRowB; + srcPtrRowR = srcPtrChannel; + srcPtrRowG = srcPtrRowR + srcDescPtr->strides.cStride; + srcPtrRowB = srcPtrRowG + srcDescPtr->strides.cStride; + dstPtrRowR = dstPtrChannel; + dstPtrRowG = dstPtrRowR + dstDescPtr->strides.cStride; + dstPtrRowB = dstPtrRowG + dstDescPtr->strides.cStride; + + for(int i = 0; i < roi.xywhROI.roiHeight; i++) + { + Rpp8s *srcPtrTempR, *srcPtrTempG, *srcPtrTempB, *dstPtrTempR, *dstPtrTempG, *dstPtrTempB; + srcPtrTempR = srcPtrRowR; + srcPtrTempG = srcPtrRowG; + srcPtrTempB = srcPtrRowB; + dstPtrTempR = dstPtrRowR; + dstPtrTempG = dstPtrRowG; + dstPtrTempB = dstPtrRowB; + + Rpp32s iLoc = i - halfHeight; + Rpp32f iLocComponent = iLoc * iLoc; + __m256 pILocComponent = _mm256_set1_ps(iLocComponent); + __m256 pJLocComponent = _mm256_sub_ps(avx_pDstLocInit , pHalfWidth); + + int vectorLoopCount = 0; + for (; vectorLoopCount < alignedLength; vectorLoopCount += 16) + { + __m256 p[6]; + rpp_simd_load(rpp_load48_i8pln3_to_f32pln3_avx, srcPtrTempR, srcPtrTempG, srcPtrTempB, p); // simd loads + compute_vignette_48_host(p, pMultiplier, pILocComponent, pJLocComponent); // vignette adjustment + rpp_simd_store(rpp_store48_f32pln3_to_i8pln3_avx, dstPtrTempR, dstPtrTempG, dstPtrTempB, p); // simd stores + srcPtrTempR += 16; + srcPtrTempG += 16; + srcPtrTempB += 16; + dstPtrTempR += 16; + dstPtrTempG += 16; + dstPtrTempB += 16; + } + for (; vectorLoopCount < bufferLength; vectorLoopCount++) + { + Rpp32s jLoc = vectorLoopCount - halfWidth; + Rpp32f jLocComponent = jLoc * jLoc; + Rpp32f gaussianValue = std::exp((iLocComponent * multiplier)) * std::exp((jLocComponent * multiplier)); + Rpp32f srcPtrTempI8[3]; + srcPtrTempI8[0] = (Rpp32f)*srcPtrTempR + 128; + srcPtrTempI8[1] = (Rpp32f)*srcPtrTempG + 128; + srcPtrTempI8[2] = (Rpp32f)*srcPtrTempB + 128; + + *dstPtrTempR++ = (Rpp8s) RPPPIXELCHECKI8(nearbyintf(srcPtrTempI8[0] * gaussianValue) - 128); + *dstPtrTempG++ = (Rpp8s) RPPPIXELCHECKI8(nearbyintf(srcPtrTempI8[1] * gaussianValue) - 128); + *dstPtrTempB++ = (Rpp8s) RPPPIXELCHECKI8(nearbyintf(srcPtrTempI8[2] * gaussianValue) - 128); + + srcPtrTempR++; + srcPtrTempG++; + srcPtrTempB++; + } + + srcPtrRowR += srcDescPtr->strides.hStride; + srcPtrRowG += srcDescPtr->strides.hStride; + srcPtrRowB += srcDescPtr->strides.hStride; + dstPtrRowR += dstDescPtr->strides.hStride; + dstPtrRowG += dstDescPtr->strides.hStride; + dstPtrRowB += dstDescPtr->strides.hStride; + } + } + + // Vignette without fused output-layout toggle single channel (NCHW -> NCHW) + else if ((srcDescPtr->c == 1) && (srcDescPtr->layout == RpptLayout::NCHW) && (dstDescPtr->layout == RpptLayout::NCHW)) + { + Rpp8s *srcPtrRow, *dstPtrRow; + srcPtrRow = srcPtrChannel; + dstPtrRow = dstPtrChannel; + for(int i = 0; i < roi.xywhROI.roiHeight; i++) + { + Rpp8s *srcPtrTemp, *dstPtrTemp; + srcPtrTemp = srcPtrRow; + dstPtrTemp = dstPtrRow; + + Rpp32s iLoc = i - halfHeight; + Rpp32f iLocComponent = iLoc * iLoc; + __m256 pILocComponent = _mm256_set1_ps(iLocComponent); + __m256 pJLocComponent = _mm256_sub_ps(avx_pDstLocInit , pHalfWidth); + + int vectorLoopCount = 0; + for (; vectorLoopCount < alignedLength; vectorLoopCount += 16) + { + __m256 p[2]; + rpp_simd_load(rpp_load16_i8_to_f32_avx, srcPtrTemp, p); // simd loads + compute_vignette_16_host(p, pMultiplier, pILocComponent, pJLocComponent); // vignette adjustment + rpp_simd_store(rpp_store16_f32_to_i8_avx, dstPtrTemp, p); // simd stores + srcPtrTemp += 16; + dstPtrTemp += 16; + } + for (; vectorLoopCount < bufferLength; vectorLoopCount++) + { + Rpp32s jLoc = vectorLoopCount - halfWidth; + Rpp32f jLocComponent = jLoc * jLoc; + Rpp32f gaussianValue = std::exp((iLocComponent * multiplier)) * std::exp((jLocComponent * multiplier)); + Rpp32f srcPtrTempI8; + srcPtrTempI8 = (Rpp32f)*srcPtrTemp + 128; + + *dstPtrTemp++ = (Rpp8s) RPPPIXELCHECKI8(nearbyintf(srcPtrTempI8 * gaussianValue) - 128); + srcPtrTemp++; + } + + srcPtrRow += srcDescPtr->strides.hStride; + dstPtrRow += dstDescPtr->strides.hStride; + } + } + } + + return RPP_SUCCESS; +} + +RppStatus vignette_f16_f16_host_tensor(Rpp16f *srcPtr, + RpptDescPtr srcDescPtr, + Rpp16f *dstPtr, + RpptDescPtr dstDescPtr, + Rpp32f *vignetteIntensityTensor, + 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); + + Rpp32f intensity = vignetteIntensityTensor[batchCount]; + Rpp32s halfHeight = (Rpp32s) (roi.xywhROI.roiHeight >> 1); + Rpp32s halfWidth = (Rpp32s) (roi.xywhROI.roiWidth >> 1); + Rpp32f radius = std::max(halfHeight, halfWidth); + Rpp32f multiplier = -(0.25f * intensity) / (radius * radius); + + Rpp16f *srcPtrImage, *dstPtrImage; + srcPtrImage = srcPtr + batchCount * srcDescPtr->strides.nStride; + dstPtrImage = dstPtr + batchCount * dstDescPtr->strides.nStride; + + Rpp32u bufferLength = roi.xywhROI.roiWidth; + Rpp32u alignedLength = bufferLength & ~7; + + Rpp16f *srcPtrChannel, *dstPtrChannel; + srcPtrChannel = srcPtrImage + (roi.xywhROI.xy.y * srcDescPtr->strides.hStride) + (roi.xywhROI.xy.x * layoutParams.bufferMultiplier); + dstPtrChannel = dstPtrImage; + + __m256 pMultiplier = _mm256_set1_ps(multiplier); + __m256 pHalfWidth = _mm256_set1_ps(halfWidth); + + // Vignette with fused output-layout toggle (NHWC -> NCHW) + if ((srcDescPtr->c == 3) && (srcDescPtr->layout == RpptLayout::NHWC) && (dstDescPtr->layout == RpptLayout::NCHW)) + { + Rpp16f *srcPtrRow, *dstPtrRowR, *dstPtrRowG, *dstPtrRowB; + srcPtrRow = srcPtrChannel; + dstPtrRowR = dstPtrChannel; + dstPtrRowG = dstPtrRowR + dstDescPtr->strides.cStride; + dstPtrRowB = dstPtrRowG + dstDescPtr->strides.cStride; + + for(int i = 0; i < roi.xywhROI.roiHeight; i++) + { + Rpp16f *srcPtrTemp, *dstPtrTempR, *dstPtrTempG, *dstPtrTempB; + srcPtrTemp = srcPtrRow; + dstPtrTempR = dstPtrRowR; + dstPtrTempG = dstPtrRowG; + dstPtrTempB = dstPtrRowB; + + Rpp32s iLoc = i - halfHeight; + Rpp32f iLocComponent = iLoc * iLoc; + __m256 pILocComponent = _mm256_set1_ps(iLocComponent); + __m256 pJLocComponent = _mm256_sub_ps(avx_pDstLocInit , pHalfWidth); + + int vectorLoopCount = 0; + for (; vectorLoopCount < alignedLength; vectorLoopCount += 8) + { + Rpp32f srcPtrTemp_ps[24]; + Rpp32f dstPtrTempR_ps[8], dstPtrTempG_ps[8], dstPtrTempB_ps[8]; + for(int cnt = 0; cnt < 24; cnt++) + { + srcPtrTemp_ps[cnt] = (Rpp32f) srcPtrTemp[cnt]; + } + __m256 p[3]; + rpp_simd_load(rpp_load24_f32pkd3_to_f32pln3_avx, srcPtrTemp_ps, p); // simd loads + compute_vignette_24_host(p, pMultiplier, pILocComponent, pJLocComponent); // vignette adjustment + rpp_simd_store(rpp_store24_f32pln3_to_f32pln3_avx, dstPtrTempR_ps, dstPtrTempG_ps, dstPtrTempB_ps, p); // simd stores + for(int cnt = 0; cnt < 8; cnt++) + { + dstPtrTempR[cnt] = (Rpp16f) dstPtrTempR_ps[cnt]; + dstPtrTempG[cnt] = (Rpp16f) dstPtrTempG_ps[cnt]; + dstPtrTempB[cnt] = (Rpp16f) dstPtrTempB_ps[cnt]; + } + srcPtrTemp += 24; + dstPtrTempR += 8; + dstPtrTempG += 8; + dstPtrTempB += 8; + } + for (; vectorLoopCount < bufferLength; vectorLoopCount++) + { + Rpp32s jLoc = vectorLoopCount - halfWidth; + Rpp32f jLocComponent = jLoc * jLoc; + Rpp32f gaussianValue = std::exp((iLocComponent * multiplier)) * std::exp((jLocComponent * multiplier)); + + *dstPtrTempR++ = (Rpp16f) RPPPIXELCHECKF32(srcPtrTemp[0] * gaussianValue); + *dstPtrTempG++ = (Rpp16f) RPPPIXELCHECKF32(srcPtrTemp[1] * gaussianValue); + *dstPtrTempB++ = (Rpp16f) RPPPIXELCHECKF32(srcPtrTemp[2] * gaussianValue); + + srcPtrTemp += 3; + } + + srcPtrRow += srcDescPtr->strides.hStride; + dstPtrRowR += dstDescPtr->strides.hStride; + dstPtrRowG += dstDescPtr->strides.hStride; + dstPtrRowB += dstDescPtr->strides.hStride; + } + } + + // Vignette with fused output-layout toggle (NCHW -> NHWC) + else if ((srcDescPtr->c == 3) && (srcDescPtr->layout == RpptLayout::NCHW) && (dstDescPtr->layout == RpptLayout::NHWC)) + { + Rpp16f *srcPtrRowR, *srcPtrRowG, *srcPtrRowB, *dstPtrRow; + srcPtrRowR = srcPtrChannel; + srcPtrRowG = srcPtrRowR + srcDescPtr->strides.cStride; + srcPtrRowB = srcPtrRowG + srcDescPtr->strides.cStride; + dstPtrRow = dstPtrChannel; + for(int i = 0; i < roi.xywhROI.roiHeight; i++) + { + Rpp16f *srcPtrTempR, *srcPtrTempG, *srcPtrTempB, *dstPtrTemp; + srcPtrTempR = srcPtrRowR; + srcPtrTempG = srcPtrRowG; + srcPtrTempB = srcPtrRowB; + dstPtrTemp = dstPtrRow; + + Rpp32s iLoc = i - halfHeight; + Rpp32f iLocComponent = iLoc * iLoc; + __m256 pILocComponent = _mm256_set1_ps(iLocComponent); + __m256 pJLocComponent = _mm256_sub_ps(avx_pDstLocInit , pHalfWidth); + + int vectorLoopCount = 0; + for (; vectorLoopCount < alignedLength; vectorLoopCount += 8) + { + Rpp32f srcPtrTempR_ps[8], srcPtrTempG_ps[8], srcPtrTempB_ps[8]; + Rpp32f dstPtrTemp_ps[25]; + for(int cnt = 0; cnt < 8; cnt++) + { + srcPtrTempR_ps[cnt] = (Rpp32f) srcPtrTempR[cnt]; + srcPtrTempG_ps[cnt] = (Rpp32f) srcPtrTempG[cnt]; + srcPtrTempB_ps[cnt] = (Rpp32f) srcPtrTempB[cnt]; + } + __m256 p[3]; + rpp_simd_load(rpp_load24_f32pln3_to_f32pln3_avx, srcPtrTempR_ps, srcPtrTempG_ps, srcPtrTempB_ps, p); // simd loads + compute_vignette_24_host(p, pMultiplier, pILocComponent, pJLocComponent); // vignette adjustment + rpp_simd_store(rpp_store24_f32pln3_to_f32pkd3_avx, dstPtrTemp_ps, p); // simd stores + for(int cnt = 0; cnt < 24; cnt++) + dstPtrTemp[cnt] = (Rpp16f) dstPtrTemp_ps[cnt]; + srcPtrTempR += 8; + srcPtrTempG += 8; + srcPtrTempB += 8; + dstPtrTemp += 24; + } + for (; vectorLoopCount < bufferLength; vectorLoopCount++) + { + Rpp32s jLoc = vectorLoopCount - halfWidth; + Rpp32f jLocComponent = jLoc * jLoc; + Rpp32f gaussianValue = std::exp((iLocComponent * multiplier)) * std::exp((jLocComponent * multiplier)); + + dstPtrTemp[0] = (Rpp16f) RPPPIXELCHECKF32(*srcPtrTempR * gaussianValue); + dstPtrTemp[1] = (Rpp16f) RPPPIXELCHECKF32(*srcPtrTempG * gaussianValue); + dstPtrTemp[2] = (Rpp16f) RPPPIXELCHECKF32(*srcPtrTempB * gaussianValue); + + srcPtrTempR++; + srcPtrTempG++; + srcPtrTempB++; + dstPtrTemp += 3; + } + + srcPtrRowR += srcDescPtr->strides.hStride; + srcPtrRowG += srcDescPtr->strides.hStride; + srcPtrRowB += srcDescPtr->strides.hStride; + dstPtrRow += dstDescPtr->strides.hStride; + } + } + + // Vignette with fused output-layout toggle (NHWC -> NHWC) + else if ((srcDescPtr->c == 3) && (srcDescPtr->layout == RpptLayout::NHWC) && (dstDescPtr->layout == RpptLayout::NHWC)) + { + Rpp16f *srcPtrRow, *dstPtrRow; + srcPtrRow = srcPtrChannel; + dstPtrRow = dstPtrChannel; + for(int i = 0; i < roi.xywhROI.roiHeight; i++) + { + Rpp16f *srcPtrTemp, *dstPtrTemp; + srcPtrTemp = srcPtrRow; + dstPtrTemp = dstPtrRow; + + Rpp32s iLoc = i - halfHeight; + Rpp32f iLocComponent = iLoc * iLoc; + __m256 pILocComponent = _mm256_set1_ps(iLocComponent); + __m256 pJLocComponent = _mm256_sub_ps(avx_pDstLocInit , pHalfWidth); + + int vectorLoopCount = 0; + for (; vectorLoopCount < alignedLength; vectorLoopCount += 8) + { + Rpp32f srcPtrTemp_ps[24]; + Rpp32f dstPtrTemp_ps[25]; + for(int cnt = 0; cnt < 24; cnt++) + { + srcPtrTemp_ps[cnt] = (Rpp32f) srcPtrTemp[cnt]; + } + __m256 p[3]; + rpp_simd_load(rpp_load24_f32pkd3_to_f32pln3_avx, srcPtrTemp_ps, p); // simd loads + compute_vignette_24_host(p, pMultiplier, pILocComponent, pJLocComponent); // vignette adjustment + rpp_simd_store(rpp_store24_f32pln3_to_f32pkd3_avx, dstPtrTemp_ps, p); // simd stores + for(int cnt = 0; cnt < 24; cnt++) + dstPtrTemp[cnt] = (Rpp16f) dstPtrTemp_ps[cnt]; + srcPtrTemp += 24; + dstPtrTemp += 24; + } + for (; vectorLoopCount < bufferLength; vectorLoopCount++) + { + Rpp32s jLoc = vectorLoopCount - halfWidth; + Rpp32f jLocComponent = jLoc * jLoc; + Rpp32f gaussianValue = std::exp((iLocComponent * multiplier)) * std::exp((jLocComponent * multiplier)); + + dstPtrTemp[0] = (Rpp16f) RPPPIXELCHECKF32(srcPtrTemp[0] * gaussianValue); + dstPtrTemp[1] = (Rpp16f) RPPPIXELCHECKF32(srcPtrTemp[1] * gaussianValue); + dstPtrTemp[2] = (Rpp16f) RPPPIXELCHECKF32(srcPtrTemp[2] * gaussianValue); + + srcPtrTemp += 3; + dstPtrTemp += 3; + } + + srcPtrRow += srcDescPtr->strides.hStride; + dstPtrRow += dstDescPtr->strides.hStride; + } + } + + // Vignette with fused output-layout toggle (NCHW -> NCHW) + else if ((srcDescPtr->c == 3) && (srcDescPtr->layout == RpptLayout::NCHW) && (dstDescPtr->layout == RpptLayout::NCHW)) + { + Rpp16f *srcPtrRowR, *srcPtrRowG, *srcPtrRowB, *dstPtrRowR, *dstPtrRowG, *dstPtrRowB; + srcPtrRowR = srcPtrChannel; + srcPtrRowG = srcPtrRowR + srcDescPtr->strides.cStride; + srcPtrRowB = srcPtrRowG + srcDescPtr->strides.cStride; + dstPtrRowR = dstPtrChannel; + dstPtrRowG = dstPtrRowR + dstDescPtr->strides.cStride; + dstPtrRowB = dstPtrRowG + dstDescPtr->strides.cStride; + + for(int i = 0; i < roi.xywhROI.roiHeight; i++) + { + Rpp16f *srcPtrTempR, *srcPtrTempG, *srcPtrTempB, *dstPtrTempR, *dstPtrTempG, *dstPtrTempB; + srcPtrTempR = srcPtrRowR; + srcPtrTempG = srcPtrRowG; + srcPtrTempB = srcPtrRowB; + dstPtrTempR = dstPtrRowR; + dstPtrTempG = dstPtrRowG; + dstPtrTempB = dstPtrRowB; + + Rpp32s iLoc = i - halfHeight; + Rpp32f iLocComponent = iLoc * iLoc; + __m256 pILocComponent = _mm256_set1_ps(iLocComponent); + __m256 pJLocComponent = _mm256_sub_ps(avx_pDstLocInit , pHalfWidth); + + int vectorLoopCount = 0; + for (; vectorLoopCount < alignedLength; vectorLoopCount += 8) + { + Rpp32f srcPtrTempR_ps[8], srcPtrTempG_ps[8], srcPtrTempB_ps[8]; + Rpp32f dstPtrTempR_ps[8], dstPtrTempG_ps[8], dstPtrTempB_ps[8]; + for(int cnt = 0; cnt < 8; cnt++) + { + srcPtrTempR_ps[cnt] = (Rpp32f) srcPtrTempR[cnt]; + srcPtrTempG_ps[cnt] = (Rpp32f) srcPtrTempG[cnt]; + srcPtrTempB_ps[cnt] = (Rpp32f) srcPtrTempB[cnt]; + } + __m256 p[6]; + rpp_simd_load(rpp_load24_f32pln3_to_f32pln3_avx, srcPtrTempR_ps, srcPtrTempG_ps, srcPtrTempB_ps, p); // simd loads + compute_vignette_24_host(p, pMultiplier, pILocComponent, pJLocComponent); // vignette adjustment + rpp_simd_store(rpp_store24_f32pln3_to_f32pln3_avx, dstPtrTempR_ps, dstPtrTempG_ps, dstPtrTempB_ps, p); // simd stores + for(int cnt = 0; cnt < 8; cnt++) + { + dstPtrTempR[cnt] = (Rpp16f) dstPtrTempR_ps[cnt]; + dstPtrTempG[cnt] = (Rpp16f) dstPtrTempG_ps[cnt]; + dstPtrTempB[cnt] = (Rpp16f) dstPtrTempB_ps[cnt]; + } + srcPtrTempR += 8; + srcPtrTempG += 8; + srcPtrTempB += 8; + dstPtrTempR += 8; + dstPtrTempG += 8; + dstPtrTempB += 8; + } + for (; vectorLoopCount < bufferLength; vectorLoopCount++) + { + Rpp32s jLoc = vectorLoopCount - halfWidth; + Rpp32f jLocComponent = jLoc * jLoc; + Rpp32f gaussianValue = std::exp((iLocComponent * multiplier)) * std::exp((jLocComponent * multiplier)); + + *dstPtrTempR++ = (Rpp16f) RPPPIXELCHECKF32(*srcPtrTempR * gaussianValue); + *dstPtrTempG++ = (Rpp16f) RPPPIXELCHECKF32(*srcPtrTempG * gaussianValue); + *dstPtrTempB++ = (Rpp16f) RPPPIXELCHECKF32(*srcPtrTempB * gaussianValue); + + srcPtrTempR++; + srcPtrTempG++; + srcPtrTempB++; + } + + srcPtrRowR += srcDescPtr->strides.hStride; + srcPtrRowG += srcDescPtr->strides.hStride; + srcPtrRowB += srcDescPtr->strides.hStride; + dstPtrRowR += dstDescPtr->strides.hStride; + dstPtrRowG += dstDescPtr->strides.hStride; + dstPtrRowB += dstDescPtr->strides.hStride; + } + } + + // Vignette without fused output-layout toggle single channel (NCHW -> NCHW) + else if ((srcDescPtr->c == 1) && (srcDescPtr->layout == RpptLayout::NCHW) && (dstDescPtr->layout == RpptLayout::NCHW)) + { + Rpp16f *srcPtrRow, *dstPtrRow; + srcPtrRow = srcPtrChannel; + dstPtrRow = dstPtrChannel; + for(int i = 0; i < roi.xywhROI.roiHeight; i++) + { + Rpp16f *srcPtrTemp, *dstPtrTemp; + srcPtrTemp = srcPtrRow; + dstPtrTemp = dstPtrRow; + + Rpp32s iLoc = i - halfHeight; + Rpp32f iLocComponent = iLoc * iLoc; + __m256 pILocComponent = _mm256_set1_ps(iLocComponent); + __m256 pJLocComponent = _mm256_sub_ps(avx_pDstLocInit , pHalfWidth); + + int vectorLoopCount = 0; + for (; vectorLoopCount < alignedLength; vectorLoopCount += 8) + { + Rpp32f srcPtrTemp_ps[8], dstPtrTemp_ps[8]; + for(int cnt = 0; cnt < 8; cnt++) + { + srcPtrTemp_ps[cnt] = (Rpp32f) srcPtrTemp[cnt]; + } + __m256 p[2]; + rpp_simd_load(rpp_load8_f32_to_f32_avx, srcPtrTemp_ps, p); // simd loads + compute_vignette_8_host(p, pMultiplier, pILocComponent, pJLocComponent); // vignette adjustment + rpp_simd_store(rpp_store8_f32_to_f32_avx, dstPtrTemp_ps, p); // simd stores + for(int cnt = 0; cnt < 8; cnt++) + dstPtrTemp[cnt] = (Rpp16f) dstPtrTemp_ps[cnt]; + srcPtrTemp += 8; + dstPtrTemp += 8; + } + for (; vectorLoopCount < bufferLength; vectorLoopCount++) + { + Rpp32s jLoc = vectorLoopCount - halfWidth; + Rpp32f jLocComponent = jLoc * jLoc; + Rpp32f gaussianValue = std::exp((iLocComponent * multiplier)) * std::exp((jLocComponent * multiplier)); + *dstPtrTemp++ = (Rpp16f) RPPPIXELCHECKF32(*srcPtrTemp * gaussianValue); + srcPtrTemp++; + } + + srcPtrRow += srcDescPtr->strides.hStride; + dstPtrRow += dstDescPtr->strides.hStride; + } + } + } + + return RPP_SUCCESS; +} \ No newline at end of file diff --git a/src/modules/hip/hip_tensor_effects_augmentations.hpp b/src/modules/hip/hip_tensor_effects_augmentations.hpp index ee62ee4dd..25cd9a863 100644 --- a/src/modules/hip/hip_tensor_effects_augmentations.hpp +++ b/src/modules/hip/hip_tensor_effects_augmentations.hpp @@ -33,5 +33,6 @@ SOFTWARE. #include "kernel/non_linear_blend.hpp" #include "kernel/water.hpp" #include "kernel/ricap.hpp" +#include "kernel/vignette.hpp" #endif // HIP_TENSOR_EFFECTS_AUGMENTATIONS_HPP diff --git a/src/modules/hip/kernel/vignette.hpp b/src/modules/hip/kernel/vignette.hpp new file mode 100644 index 000000000..06e9995e3 --- /dev/null +++ b/src/modules/hip/kernel/vignette.hpp @@ -0,0 +1,306 @@ +#include +#include "rpp_hip_common.hpp" + +__device__ void vignette_gaussian_hip_compute(float &multiplier, int2 &halfDimsWH_i2, int2 &idXY_i2, d_float8 *gaussianValue_f8) +{ + float rowLocComponent; + rowLocComponent = idXY_i2.y - halfDimsWH_i2.y; + rowLocComponent *= (rowLocComponent); + + float4 rowLocComponent_f4 = static_cast(rowLocComponent); + float4 multiplier_f4 = static_cast(multiplier); + + d_float8 colLocComponent_f8; + colLocComponent_f8.f4[0] = make_float4(idXY_i2.x, idXY_i2.x + 1, idXY_i2.x + 2, idXY_i2.x + 3); + colLocComponent_f8.f4[1] = colLocComponent_f8.f4[0] + static_cast(4); + colLocComponent_f8.f4[0] -= static_cast(halfDimsWH_i2.x); + colLocComponent_f8.f4[1] -= static_cast(halfDimsWH_i2.x); + colLocComponent_f8.f4[0] = (colLocComponent_f8.f4[0] * colLocComponent_f8.f4[0]) + rowLocComponent_f4; + colLocComponent_f8.f4[1] = (colLocComponent_f8.f4[1] * colLocComponent_f8.f4[1]) + rowLocComponent_f4; + colLocComponent_f8.f4[0] = colLocComponent_f8.f4[0] * multiplier_f4; + colLocComponent_f8.f4[1] = colLocComponent_f8.f4[1] * multiplier_f4; + + gaussianValue_f8->f4[0] = make_float4(expf(colLocComponent_f8.f4[0].x), expf(colLocComponent_f8.f4[0].y), expf(colLocComponent_f8.f4[0].z), expf(colLocComponent_f8.f4[0].w)); + gaussianValue_f8->f4[1] = make_float4(expf(colLocComponent_f8.f4[1].x), expf(colLocComponent_f8.f4[1].y), expf(colLocComponent_f8.f4[1].z), expf(colLocComponent_f8.f4[1].w)); +} + +__device__ void vignette_8_hip_compute(uchar *srcPtr, d_float8 *src_f8, d_float8 *dst_f8, d_float8 *gaussianValue_f8) +{ + dst_f8->f4[0] = src_f8->f4[0] * gaussianValue_f8->f4[0]; + dst_f8->f4[1] = src_f8->f4[1] * gaussianValue_f8->f4[1]; + rpp_hip_math_nearbyintf8(dst_f8, dst_f8); + rpp_hip_pixel_check_0to255(dst_f8); +} + +__device__ void vignette_8_hip_compute(float *srcPtr, d_float8 *src_f8, d_float8 *dst_f8, d_float8 *gaussianValue_f8) +{ + dst_f8->f4[0] = src_f8->f4[0] * gaussianValue_f8->f4[0]; + dst_f8->f4[1] = src_f8->f4[1] * gaussianValue_f8->f4[1]; + rpp_hip_pixel_check_0to1(dst_f8); +} + +__device__ void vignette_8_hip_compute(signed char *srcPtr, d_float8 *src_f8, d_float8 *dst_f8, d_float8 *gaussianValue_f8) +{ + float4 i8Offset_f4 = static_cast(128.0f); + rpp_hip_math_add8_const(src_f8, src_f8, i8Offset_f4); + dst_f8->f4[0] = src_f8->f4[0] * gaussianValue_f8->f4[0]; + dst_f8->f4[1] = src_f8->f4[1] * gaussianValue_f8->f4[1]; + rpp_hip_math_nearbyintf8(dst_f8, dst_f8); + rpp_hip_pixel_check_0to255(dst_f8); + rpp_hip_math_subtract8_const(dst_f8, dst_f8, i8Offset_f4); +} + +__device__ void vignette_8_hip_compute(half *srcPtr, d_float8 *src_f8, d_float8 *dst_f8, d_float8 *gaussianValue_f8) +{ + dst_f8->f4[0] = src_f8->f4[0] * gaussianValue_f8->f4[0]; + dst_f8->f4[1] = src_f8->f4[1] * gaussianValue_f8->f4[1]; + rpp_hip_pixel_check_0to1(dst_f8); +} + +template +__device__ void vignette_24_hip_compute(T *srcPtr, d_float24 *src_f24, d_float24 *dst_f24, d_float8 *gaussianValue_f8) +{ + vignette_8_hip_compute(srcPtr, &(src_f24->f8[0]), &(dst_f24->f8[0]), gaussianValue_f8); + vignette_8_hip_compute(srcPtr, &(src_f24->f8[1]), &(dst_f24->f8[1]), gaussianValue_f8); + vignette_8_hip_compute(srcPtr, &(src_f24->f8[2]), &(dst_f24->f8[2]), gaussianValue_f8); +} + +template +__global__ void vignette_pkd_tensor(T *srcPtr, + uint2 srcStridesNH, + T *dstPtr, + uint2 dstStridesNH, + float *vignetteIntensity, + RpptROIPtr roiTensorPtrSrc) +{ + int id_x = (hipBlockIdx_x * hipBlockDim_x + hipThreadIdx_x) * 8; + int id_y = hipBlockIdx_y * hipBlockDim_y + hipThreadIdx_y; + int id_z = hipBlockIdx_z * hipBlockDim_z + hipThreadIdx_z; + + if ((id_y >= roiTensorPtrSrc[id_z].xywhROI.roiHeight) || (id_x >= roiTensorPtrSrc[id_z].xywhROI.roiWidth)) + { + return; + } + + uint srcIdx = (id_z * srcStridesNH.x) + ((id_y + roiTensorPtrSrc[id_z].xywhROI.xy.y) * srcStridesNH.y) + ((id_x + roiTensorPtrSrc[id_z].xywhROI.xy.x) * 3); + uint dstIdx = (id_z * dstStridesNH.x) + (id_y * dstStridesNH.y) + id_x * 3; + + float intensity = vignetteIntensity[id_z]; + int2 halfDimsWH_i2 = make_int2(roiTensorPtrSrc[id_z].xywhROI.roiWidth >> 1, roiTensorPtrSrc[id_z].xywhROI.roiHeight >> 1); + int2 idXY_i2 = make_int2(id_x, id_y); + float radius = fmaxf(halfDimsWH_i2.x, halfDimsWH_i2.y); + float multiplier = -(0.25f * intensity) / (radius * radius); + + d_float24 src_f24, dst_f24; + d_float8 gaussianValue_f8; + vignette_gaussian_hip_compute(multiplier, halfDimsWH_i2, idXY_i2, &gaussianValue_f8); + rpp_hip_load24_pkd3_and_unpack_to_float24_pln3(srcPtr + srcIdx, &src_f24); + vignette_24_hip_compute(srcPtr, &src_f24, &dst_f24, &gaussianValue_f8); + rpp_hip_pack_float24_pln3_and_store24_pkd3(dstPtr + dstIdx, &dst_f24); +} + +template +__global__ void vignette_pln_tensor(T *srcPtr, + uint3 srcStridesNCH, + T *dstPtr, + uint3 dstStridesNCH, + int channelsDst, + float *vignetteIntensity, + RpptROIPtr roiTensorPtrSrc) +{ + int id_x = (hipBlockIdx_x * hipBlockDim_x + hipThreadIdx_x) * 8; + int id_y = hipBlockIdx_y * hipBlockDim_y + hipThreadIdx_y; + int id_z = hipBlockIdx_z * hipBlockDim_z + hipThreadIdx_z; + + if ((id_y >= roiTensorPtrSrc[id_z].xywhROI.roiHeight) || (id_x >= roiTensorPtrSrc[id_z].xywhROI.roiWidth)) + { + return; + } + + uint srcIdx = (id_z * srcStridesNCH.x) + ((id_y + roiTensorPtrSrc[id_z].xywhROI.xy.y) * srcStridesNCH.z) + (id_x + roiTensorPtrSrc[id_z].xywhROI.xy.x); + uint dstIdx = (id_z * dstStridesNCH.x) + (id_y * dstStridesNCH.z) + id_x; + + float intensity = vignetteIntensity[id_z]; + int2 halfDimsWH_i2 = make_int2(roiTensorPtrSrc[id_z].xywhROI.roiWidth >> 1, roiTensorPtrSrc[id_z].xywhROI.roiHeight >> 1); + int2 idXY_i2 = make_int2(id_x, id_y); + float radius = fmaxf(halfDimsWH_i2.x, halfDimsWH_i2.y); + float multiplier = -(0.25f * intensity) / (radius * radius); + + d_float8 src_f8, dst_f8; + d_float8 gaussianValue_f8; + vignette_gaussian_hip_compute(multiplier, halfDimsWH_i2, idXY_i2, &gaussianValue_f8); + + rpp_hip_load8_and_unpack_to_float8(srcPtr + srcIdx, &src_f8); + vignette_8_hip_compute(srcPtr, &src_f8, &dst_f8, &gaussianValue_f8); + rpp_hip_pack_float8_and_store8(dstPtr + dstIdx, &dst_f8); + + if (channelsDst == 3) + { + srcIdx += srcStridesNCH.y; + dstIdx += dstStridesNCH.y; + + rpp_hip_load8_and_unpack_to_float8(srcPtr + srcIdx, &src_f8); + vignette_8_hip_compute(srcPtr, &src_f8, &dst_f8, &gaussianValue_f8); + rpp_hip_pack_float8_and_store8(dstPtr + dstIdx, &dst_f8); + + srcIdx += srcStridesNCH.y; + dstIdx += dstStridesNCH.y; + + rpp_hip_load8_and_unpack_to_float8(srcPtr + srcIdx, &src_f8); + vignette_8_hip_compute(srcPtr, &src_f8, &dst_f8, &gaussianValue_f8); + rpp_hip_pack_float8_and_store8(dstPtr + dstIdx, &dst_f8); + } +} + +template +__global__ void vignette_pkd3_pln3_tensor(T *srcPtr, + uint2 srcStridesNH, + T *dstPtr, + uint3 dstStridesNCH, + float *vignetteIntensity, + RpptROIPtr roiTensorPtrSrc) +{ + int id_x = (hipBlockIdx_x * hipBlockDim_x + hipThreadIdx_x) * 8; + int id_y = hipBlockIdx_y * hipBlockDim_y + hipThreadIdx_y; + int id_z = hipBlockIdx_z * hipBlockDim_z + hipThreadIdx_z; + + if ((id_y >= roiTensorPtrSrc[id_z].xywhROI.roiHeight) || (id_x >= roiTensorPtrSrc[id_z].xywhROI.roiWidth)) + { + return; + } + + uint srcIdx = (id_z * srcStridesNH.x) + ((id_y + roiTensorPtrSrc[id_z].xywhROI.xy.y) * srcStridesNH.y) + ((id_x + roiTensorPtrSrc[id_z].xywhROI.xy.x) * 3); + uint dstIdx = (id_z * dstStridesNCH.x) + (id_y * dstStridesNCH.z) + id_x; + + float intensity = vignetteIntensity[id_z]; + int2 halfDimsWH_i2 = make_int2(roiTensorPtrSrc[id_z].xywhROI.roiWidth >> 1, roiTensorPtrSrc[id_z].xywhROI.roiHeight >> 1); + int2 idXY_i2 = make_int2(id_x, id_y); + float radius = fmaxf(halfDimsWH_i2.x, halfDimsWH_i2.y); + float multiplier = -(0.25f * intensity) / (radius * radius); + + d_float24 src_f24, dst_f24; + d_float8 gaussianValue_f8; + vignette_gaussian_hip_compute(multiplier, halfDimsWH_i2, idXY_i2, &gaussianValue_f8); + + rpp_hip_load24_pkd3_and_unpack_to_float24_pln3(srcPtr + srcIdx, &src_f24); + vignette_24_hip_compute(srcPtr, &src_f24, &dst_f24, &gaussianValue_f8); + rpp_hip_pack_float24_pln3_and_store24_pln3(dstPtr + dstIdx, dstStridesNCH.y, &dst_f24); +} + +template +__global__ void vignette_pln3_pkd3_tensor(T *srcPtr, + uint3 srcStridesNCH, + T *dstPtr, + uint2 dstStridesNH, + float *vignetteIntensity, + RpptROIPtr roiTensorPtrSrc) +{ + int id_x = (hipBlockIdx_x * hipBlockDim_x + hipThreadIdx_x) * 8; + int id_y = hipBlockIdx_y * hipBlockDim_y + hipThreadIdx_y; + int id_z = hipBlockIdx_z * hipBlockDim_z + hipThreadIdx_z; + + if ((id_y >= roiTensorPtrSrc[id_z].xywhROI.roiHeight) || (id_x >= roiTensorPtrSrc[id_z].xywhROI.roiWidth)) + { + return; + } + + uint srcIdx = (id_z * srcStridesNCH.x) + ((id_y + roiTensorPtrSrc[id_z].xywhROI.xy.y) * srcStridesNCH.z) + (id_x + roiTensorPtrSrc[id_z].xywhROI.xy.x); + uint dstIdx = (id_z * dstStridesNH.x) + (id_y * dstStridesNH.y) + id_x * 3; + + float intensity = vignetteIntensity[id_z]; + int2 halfDimsWH_i2 = make_int2(roiTensorPtrSrc[id_z].xywhROI.roiWidth >> 1, roiTensorPtrSrc[id_z].xywhROI.roiHeight >> 1); + int2 idXY_i2 = make_int2(id_x, id_y); + float radius = fmaxf(halfDimsWH_i2.x, halfDimsWH_i2.y); + float multiplier = -(0.25f * intensity) / (radius * radius); + + d_float24 src_f24, dst_f24; + d_float8 gaussianValue_f8; + vignette_gaussian_hip_compute(multiplier, halfDimsWH_i2, idXY_i2, &gaussianValue_f8); + + rpp_hip_load24_pln3_and_unpack_to_float24_pln3(srcPtr + srcIdx, srcStridesNCH.y, &src_f24); + vignette_24_hip_compute(srcPtr, &src_f24, &dst_f24, &gaussianValue_f8); + rpp_hip_pack_float24_pln3_and_store24_pkd3(dstPtr + dstIdx, &dst_f24); +} + +template +RppStatus hip_exec_vignette_tensor(T *srcPtr, + RpptDescPtr srcDescPtr, + T *dstPtr, + RpptDescPtr dstDescPtr, + RpptROIPtr roiTensorPtrSrc, + Rpp32f *vignetteIntensityTensor, + RpptRoiType roiType, + rpp::Handle& handle) +{ + if (roiType == RpptRoiType::LTRB) + hip_exec_roi_converison_ltrb_to_xywh(roiTensorPtrSrc, handle); + + int globalThreads_x = (dstDescPtr->strides.hStride + 7) >> 3; + int globalThreads_y = dstDescPtr->h; + int globalThreads_z = handle.GetBatchSize(); + + if ((srcDescPtr->layout == RpptLayout::NHWC) && (dstDescPtr->layout == RpptLayout::NHWC)) + { + globalThreads_x = (dstDescPtr->strides.hStride / 3 + 7) >> 3; + hipLaunchKernelGGL(vignette_pkd_tensor, + dim3(ceil((float)globalThreads_x/LOCAL_THREADS_X), ceil((float)globalThreads_y/LOCAL_THREADS_Y), ceil((float)globalThreads_z/LOCAL_THREADS_Z)), + dim3(LOCAL_THREADS_X, LOCAL_THREADS_Y, LOCAL_THREADS_Z), + 0, + handle.GetStream(), + srcPtr, + make_uint2(srcDescPtr->strides.nStride, srcDescPtr->strides.hStride), + dstPtr, + make_uint2(dstDescPtr->strides.nStride, dstDescPtr->strides.hStride), + vignetteIntensityTensor, + roiTensorPtrSrc); + } + else if ((srcDescPtr->layout == RpptLayout::NCHW) && (dstDescPtr->layout == RpptLayout::NCHW)) + { + hipLaunchKernelGGL(vignette_pln_tensor, + dim3(ceil((float)globalThreads_x/LOCAL_THREADS_X), ceil((float)globalThreads_y/LOCAL_THREADS_Y), ceil((float)globalThreads_z/LOCAL_THREADS_Z)), + dim3(LOCAL_THREADS_X, LOCAL_THREADS_Y, LOCAL_THREADS_Z), + 0, + handle.GetStream(), + srcPtr, + make_uint3(srcDescPtr->strides.nStride, srcDescPtr->strides.cStride, srcDescPtr->strides.hStride), + dstPtr, + make_uint3(dstDescPtr->strides.nStride, dstDescPtr->strides.cStride, dstDescPtr->strides.hStride), + dstDescPtr->c, + vignetteIntensityTensor, + roiTensorPtrSrc); + } + else if ((srcDescPtr->c == 3) && (dstDescPtr->c == 3)) + { + if ((srcDescPtr->layout == RpptLayout::NHWC) && (dstDescPtr->layout == RpptLayout::NCHW)) + { + hipLaunchKernelGGL(vignette_pkd3_pln3_tensor, + dim3(ceil((float)globalThreads_x/LOCAL_THREADS_X), ceil((float)globalThreads_y/LOCAL_THREADS_Y), ceil((float)globalThreads_z/LOCAL_THREADS_Z)), + dim3(LOCAL_THREADS_X, LOCAL_THREADS_Y, LOCAL_THREADS_Z), + 0, + handle.GetStream(), + srcPtr, + make_uint2(srcDescPtr->strides.nStride, srcDescPtr->strides.hStride), + dstPtr, + make_uint3(dstDescPtr->strides.nStride, dstDescPtr->strides.cStride, dstDescPtr->strides.hStride), + vignetteIntensityTensor, + roiTensorPtrSrc); + } + else if ((srcDescPtr->layout == RpptLayout::NCHW) && (dstDescPtr->layout == RpptLayout::NHWC)) + { + globalThreads_x = (srcDescPtr->strides.hStride + 7) >> 3; + hipLaunchKernelGGL(vignette_pln3_pkd3_tensor, + dim3(ceil((float)globalThreads_x/LOCAL_THREADS_X), ceil((float)globalThreads_y/LOCAL_THREADS_Y), ceil((float)globalThreads_z/LOCAL_THREADS_Z)), + dim3(LOCAL_THREADS_X, LOCAL_THREADS_Y, LOCAL_THREADS_Z), + 0, + handle.GetStream(), + srcPtr, + make_uint3(srcDescPtr->strides.nStride, srcDescPtr->strides.cStride, srcDescPtr->strides.hStride), + dstPtr, + make_uint2(dstDescPtr->strides.nStride, dstDescPtr->strides.hStride), + vignetteIntensityTensor, + roiTensorPtrSrc); + } + } + + return RPP_SUCCESS; +} diff --git a/src/modules/rppt_tensor_effects_augmentations.cpp b/src/modules/rppt_tensor_effects_augmentations.cpp index 63780adba..706040d17 100644 --- a/src/modules/rppt_tensor_effects_augmentations.cpp +++ b/src/modules/rppt_tensor_effects_augmentations.cpp @@ -596,6 +596,72 @@ RppStatus rppt_water_host(RppPtr_t srcPtr, return RPP_SUCCESS; } +/******************** vignette ********************/ + +RppStatus rppt_vignette_host(RppPtr_t srcPtr, + RpptDescPtr srcDescPtr, + RppPtr_t dstPtr, + RpptDescPtr dstDescPtr, + Rpp32f *vignetteIntensityTensor, + 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)) + { + vignette_u8_u8_host_tensor(static_cast(srcPtr) + srcDescPtr->offsetInBytes, + srcDescPtr, + static_cast(dstPtr) + dstDescPtr->offsetInBytes, + dstDescPtr, + vignetteIntensityTensor, + roiTensorPtrSrc, + roiType, + layoutParams, + rpp::deref(rppHandle)); + } + else if ((srcDescPtr->dataType == RpptDataType::F16) && (dstDescPtr->dataType == RpptDataType::F16)) + { + vignette_f16_f16_host_tensor(reinterpret_cast (static_cast(srcPtr) + srcDescPtr->offsetInBytes), + srcDescPtr, + reinterpret_cast (static_cast(dstPtr) + dstDescPtr->offsetInBytes), + dstDescPtr, + vignetteIntensityTensor, + roiTensorPtrSrc, + roiType, + layoutParams, + rpp::deref(rppHandle)); + } + else if ((srcDescPtr->dataType == RpptDataType::F32) && (dstDescPtr->dataType == RpptDataType::F32)) + { + vignette_f32_f32_host_tensor(reinterpret_cast (static_cast(srcPtr) + srcDescPtr->offsetInBytes), + srcDescPtr, + reinterpret_cast (static_cast(dstPtr) + dstDescPtr->offsetInBytes), + dstDescPtr, + vignetteIntensityTensor, + roiTensorPtrSrc, + roiType, + layoutParams, + rpp::deref(rppHandle)); + } + else if ((srcDescPtr->dataType == RpptDataType::I8) && (dstDescPtr->dataType == RpptDataType::I8)) + { + vignette_i8_i8_host_tensor(static_cast(srcPtr) + srcDescPtr->offsetInBytes, + srcDescPtr, + static_cast(dstPtr) + dstDescPtr->offsetInBytes, + dstDescPtr, + vignetteIntensityTensor, + roiTensorPtrSrc, + roiType, + layoutParams, + rpp::deref(rppHandle)); + } + + return RPP_SUCCESS; + +} + /******************** ricap ********************/ RppStatus rppt_ricap_host(RppPtr_t srcPtr, @@ -1322,4 +1388,68 @@ RppStatus rppt_ricap_gpu(RppPtr_t srcPtr, #endif // backend } +/******************** vignette ********************/ + +RppStatus rppt_vignette_gpu(RppPtr_t srcPtr, + RpptDescPtr srcDescPtr, + RppPtr_t dstPtr, + RpptDescPtr dstDescPtr, + Rpp32f *vignetteIntensityTensor, + RpptROIPtr roiTensorPtrSrc, + RpptRoiType roiType, + rppHandle_t rppHandle) +{ +#ifdef HIP_COMPILE + + if ((srcDescPtr->dataType == RpptDataType::U8) && (dstDescPtr->dataType == RpptDataType::U8)) + { + hip_exec_vignette_tensor(static_cast(srcPtr) + srcDescPtr->offsetInBytes, + srcDescPtr, + static_cast(dstPtr) + dstDescPtr->offsetInBytes, + dstDescPtr, + roiTensorPtrSrc, + vignetteIntensityTensor, + roiType, + rpp::deref(rppHandle)); + } + else if ((srcDescPtr->dataType == RpptDataType::F16) && (dstDescPtr->dataType == RpptDataType::F16)) + { + hip_exec_vignette_tensor((half*) (static_cast(srcPtr) + srcDescPtr->offsetInBytes), + srcDescPtr, + (half*) (static_cast(dstPtr) + dstDescPtr->offsetInBytes), + dstDescPtr, + roiTensorPtrSrc, + vignetteIntensityTensor, + roiType, + rpp::deref(rppHandle)); + } + else if ((srcDescPtr->dataType == RpptDataType::F32) && (dstDescPtr->dataType == RpptDataType::F32)) + { + hip_exec_vignette_tensor((Rpp32f*) (static_cast(srcPtr) + srcDescPtr->offsetInBytes), + srcDescPtr, + (Rpp32f*) (static_cast(dstPtr) + dstDescPtr->offsetInBytes), + dstDescPtr, + roiTensorPtrSrc, + vignetteIntensityTensor, + roiType, + rpp::deref(rppHandle)); + } + else if ((srcDescPtr->dataType == RpptDataType::I8) && (dstDescPtr->dataType == RpptDataType::I8)) + { + hip_exec_vignette_tensor(static_cast(srcPtr) + srcDescPtr->offsetInBytes, + srcDescPtr, + static_cast(dstPtr) + dstDescPtr->offsetInBytes, + dstDescPtr, + roiTensorPtrSrc, + vignetteIntensityTensor, + roiType, + rpp::deref(rppHandle)); + } + + return RPP_SUCCESS; +#elif defined(OCL_COMPILE) + return RPP_ERROR_NOT_IMPLEMENTED; +#endif // backend +} + #endif // GPU_SUPPORT diff --git a/utilities/test_suite/HIP/Tensor_hip.cpp b/utilities/test_suite/HIP/Tensor_hip.cpp index d72f49b4a..c2f7665f2 100644 --- a/utilities/test_suite/HIP/Tensor_hip.cpp +++ b/utilities/test_suite/HIP/Tensor_hip.cpp @@ -346,6 +346,10 @@ int main(int argc, char **argv) if(testCase == 82) CHECK(hipHostMalloc(&roiPtrInputCropRegion, 4 * sizeof(RpptROI))); + Rpp32f *intensity; + if(testCase == 46) + CHECK(hipHostMalloc(&intensity, batchSize * sizeof(Rpp32f))); + // case-wise RPP API and measure time script for Unit and Performance test printf("\nRunning %s %d times (each time with a batch size of %d images) and computing mean statistics...", func.c_str(), numRuns, batchSize); for(int iterCount = 0; iterCount < noOfIterations; iterCount++) @@ -828,6 +832,21 @@ int main(int argc, char **argv) break; } + case 46: + { + testCaseName = "vignette"; + + for (i = 0; i < batchSize; i++) + intensity[i] = 6; + + startWallTime = omp_get_wtime(); + if (inputBitDepth == 0 || inputBitDepth == 1 || inputBitDepth == 2 || inputBitDepth == 5) + rppt_vignette_gpu(d_input, srcDescPtr, d_output, dstDescPtr, intensity, roiTensorPtrSrc, roiTypeSrc, handle); + else + missingFuncFlag = 1; + + break; + } case 49: { testCaseName = "box_filter"; @@ -1211,6 +1230,8 @@ int main(int argc, char **argv) CHECK(hipHostFree(roiTensorPtrSrc)); CHECK(hipHostFree(roiTensorPtrDst)); CHECK(hipHostFree(dstImgSizes)); + if(testCase == 46) + CHECK(hipHostFree(intensity)); if(testCase == 82) CHECK(hipHostFree(roiPtrInputCropRegion)); if (reductionTypeCase) diff --git a/utilities/test_suite/HIP/runTests.py b/utilities/test_suite/HIP/runTests.py index e5be154c4..4dc9023f7 100644 --- a/utilities/test_suite/HIP/runTests.py +++ b/utilities/test_suite/HIP/runTests.py @@ -21,6 +21,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ + import os import sys sys.dont_write_bytecode = True @@ -324,7 +325,7 @@ def rpp_test_suite_parser_and_validator(): subprocess.run(["make", "-j16"], cwd=".") # nosec # List of cases supported -supportedCaseList = ['0', '1', '2', '4', '8', '13', '20', '21', '23', '29', '30', '31', '34', '36', '37', '38', '39', '45', '54', '61', '63', '70', '80', '82', '83', '84', '85', '86', '87', '88', '89'] +supportedCaseList = ['0', '1', '2', '4', '8', '13', '20', '21', '23', '29', '30', '31', '34', '36', '37', '38', '39', '45', '46', '54', '61', '63', '70', '80', '82', '83', '84', '85', '86', '87', '88', '89'] # 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 e93eb143a..ffc9c0d3a 100644 --- a/utilities/test_suite/HOST/Tensor_host.cpp +++ b/utilities/test_suite/HOST/Tensor_host.cpp @@ -832,6 +832,23 @@ int main(int argc, char **argv) break; } + case 46: + { + testCaseName = "vignette"; + + Rpp32f intensity[batchSize]; + for (i = 0; i < batchSize; i++) + intensity[i] = 6; + + startWallTime = omp_get_wtime(); + startCpuTime = clock(); + if (inputBitDepth == 0 || inputBitDepth == 1 || inputBitDepth == 2 || inputBitDepth == 3 || inputBitDepth == 4 || inputBitDepth == 5) + rppt_vignette_host(input, srcDescPtr, output, dstDescPtr, intensity, roiTensorPtrSrc, roiTypeSrc, handle); + else + missingFuncFlag = 1; + + break; + } case 61: { testCaseName = "magnitude"; diff --git a/utilities/test_suite/HOST/runTests.py b/utilities/test_suite/HOST/runTests.py index 9aa92d9c4..f106a9649 100644 --- a/utilities/test_suite/HOST/runTests.py +++ b/utilities/test_suite/HOST/runTests.py @@ -281,7 +281,7 @@ def rpp_test_suite_parser_and_validator(): subprocess.run(["make", "-j16"], cwd=".") # nosec # List of cases supported -supportedCaseList = ['0', '1', '2', '4', '8', '13', '20', '21', '23', '29', '30', '31', '34', '36', '37', '38', '39', '45', '54', '61', '63', '70', '80', '81', '82', '83', '84', '85', '86', '87', '88', '89'] +supportedCaseList = ['0', '1', '2', '4', '8', '13', '20', '21', '23', '29', '30', '31', '34', '36', '37', '38', '39', '45', '46', '54', '61', '63', '70', '80', '81', '82', '83', '84', '85', '86', '87', '88', '89'] print("\n\n\n\n\n") print("##########################################################################################") diff --git a/utilities/test_suite/README.md b/utilities/test_suite/README.md index 067bedb1d..41ee5109c 100644 --- a/utilities/test_suite/README.md +++ b/utilities/test_suite/README.md @@ -127,6 +127,10 @@ python runTests.py --case_start 0 --case_end 89 --test_type 0 --qa_mode 1 --batc ``` python python runTests.py --case_list 21 36 63 --test_type 1 --qa_mode 1 --batch_size 8 --num_runs 100 ``` +- QA mode (Performance tests) - Tolerance based PASS/FAIL tests for RPP HIP/HOST functionalities checking achieved improvement in performance percentage over BatchPD versions after comparison to a threshold percentage of improvement +``` python +python runTests.py --case_list 21 36 63 --test_type 1 --qa_mode 1 --batch_size 8 --num_runs 100 +``` - Unit test mode - Unit tests allowing users to pass a path to a folder containing images, to execute the desired functionality and variant once, report RPP execution wall time, save and view output images Note: For testcase 82(RICAP) Please use images of same resolution and Batchsize > 1 RICAP dataset path: rpp/utilities/test_suite/TEST_IMAGES/three_images_150x150_src1 diff --git a/utilities/test_suite/REFERENCE_OUTPUT/vignette/vignette_u8_Tensor.bin b/utilities/test_suite/REFERENCE_OUTPUT/vignette/vignette_u8_Tensor.bin new file mode 100644 index 0000000000000000000000000000000000000000..a650144f33dd100fe77b6e795bdb4d9e08206d1a GIT binary patch literal 273600 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
#7o$CxZ+6Z%-IG&|B)FIWjn&L@niR$;oV-rSwP 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 WAIK0bOOQqCWjlNY77I8@N&i1+Er~+_ literal 0 HcmV?d00001 diff --git a/utilities/test_suite/rpp_test_suite_common.h b/utilities/test_suite/rpp_test_suite_common.h index 58fee0c5d..55fc90abf 100644 --- a/utilities/test_suite/rpp_test_suite_common.h +++ b/utilities/test_suite/rpp_test_suite_common.h @@ -87,6 +87,7 @@ std::map augmentationMap = {38, "crop_mirror_normalize"}, {39, "resize_crop_mirror"}, {45, "color_temperature"}, + {46, "vignette"}, {49, "box_filter"}, {54, "gaussian_filter"}, {61, "magnitude"},