From ca1eb5e3e70568af538bc82c17ea1190380072f6 Mon Sep 17 00:00:00 2001 From: Abishek <52214183+r-abishek@users.noreply.github.com> Date: Fri, 12 Apr 2024 09:25:41 -0700 Subject: [PATCH] RPP Crop and Patch on HOST and HIP (#314) * Add crop and patch HOST Tensor implementation * Fix output mismatches * Add golden outputs * temporary commit * fix patch region updation issue * Crop and patch changes * fixed the edge case for PLN variant * added support for remianing layout variants for crop and patch HIP * resolved bug in HOST kernel and updated golden outputs updated test suite to match with previous QA mode configuration for crop and patch * fixed the incorrect golden output for PKD3 variant * fixed bug in non vector loop for HOST toggle variant * added more comments in HIP kernel for better readability added description for crop and patch function removed unnecessary spaces in HOST kernel * removed redundant files * modified the kernel to use 1 pixel load and store due to performance issues with vectorized version * 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 crop and patch golden outputs to bin files * changed copyright from 2023 to 2024 * 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 * updated copyright for crop and patch * 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> * added memcpy + vectorized version for HIP non toggle layout variants * modified toggle variants as per the latest changes * modified variable names in hip kernels for better readability categorized kernels into 2 sections * minor change * 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 * code cleanup * added device helpers for making the code modularized added ifdefs for AVX2 part in HOST codes * moved layout toggle kernels to hip commons * revert incorrect changes happened with merge * fix build issue in HOST test suite * added missing clock timer for water in HOST test suite * remove duplicate line in readme * fixed the issue with roi updation for hip and host test suite * added doxygen output for crop and patch --------- Signed-off-by: dependabot[bot] Co-authored-by: HazarathKumarM Co-authored-by: sampath1117 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: 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 --- ...ugmentations_crop_and_patch_img150x150.png | Bin 0 -> 10703 bytes include/rppt_tensor_geometric_augmentations.h | 52 + src/include/hip/rpp_hip_common.hpp | 52 + .../host_tensor_geometric_augmentations.hpp | 1 + src/modules/cpu/kernel/crop_and_patch.hpp | 1540 +++++++++++++++++ .../hip_tensor_geometric_augmentations.hpp | 1 + src/modules/hip/kernel/crop_and_patch.hpp | 426 +++++ .../rppt_tensor_geometric_augmentations.cpp | 145 ++ utilities/test_suite/HIP/Tensor_hip.cpp | 36 +- utilities/test_suite/HIP/runTests.py | 2 +- utilities/test_suite/HOST/Tensor_host.cpp | 39 +- utilities/test_suite/HOST/runTests.py | 2 +- .../crop_and_patch_u8_Tensor.bin | Bin 0 -> 273600 bytes utilities/test_suite/rpp_test_suite_common.h | 1 + 14 files changed, 2291 insertions(+), 6 deletions(-) create mode 100644 docs/data/doxygenOutputs/geometric_augmentations_crop_and_patch_img150x150.png create mode 100644 src/modules/cpu/kernel/crop_and_patch.hpp create mode 100644 src/modules/hip/kernel/crop_and_patch.hpp create mode 100644 utilities/test_suite/REFERENCE_OUTPUT/crop_and_patch/crop_and_patch_u8_Tensor.bin diff --git a/docs/data/doxygenOutputs/geometric_augmentations_crop_and_patch_img150x150.png b/docs/data/doxygenOutputs/geometric_augmentations_crop_and_patch_img150x150.png new file mode 100644 index 0000000000000000000000000000000000000000..0dd3c4c2c6bb6f6a44624b9745b2fa1a2e347ba1 GIT binary patch literal 10703 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 + * - srcPtr1 depth ranges - Rpp8u (0 to 255), Rpp16f (0 to 1), Rpp32f (0 to 1), Rpp8s (-128 to 127). + * - srcPtr2 depth ranges - Rpp8u (0 to 255), Rpp16f (0 to 1), Rpp32f (0 to 1), Rpp8s (-128 to 127). + * - dstPtr depth ranges - Will be same depth as srcPtr. + * \image html img150x150.png Sample Input1 + * \image html img150x150_2.png Sample Input2 + * \image html geometric_augmentations_crop_and_patch_img150x150.png Sample Output + * \param [in] srcPtr1 source tensor1 in HOST memory + * \param [in] srcPtr2 source tensor2 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] 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] cropRoiTensor crop co-ordinates 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] patchRoiTensor patch co-ordinates in HOST memory, for each image in source tensor (2D tensor of size batchSize * 4, in either format - XYWH(xy.x, xy.y, roiWidth, roiHeight) or LTRB(lt.x, lt.y, rb.x, rb.y)) + * \param [in] roiType ROI type used (RpptRoiType::XYWH or RpptRoiType::LTRB) + * \param [in] rppHandle RPP HOST handle created with \ref rppCreateWithBatchSize() + * \return A \ref RppStatus enumeration. + * \retval RPP_SUCCESS Successful completion. + * \retval RPP_ERROR* Unsuccessful completion. + */ +RppStatus rppt_crop_and_patch_host(RppPtr_t srcPtr1, RppPtr_t srcPtr2, RpptDescPtr srcDescPtr, RppPtr_t dstPtr, RpptDescPtr dstDescPtr, RpptROIPtr roiTensorPtrDst, RpptROIPtr cropRoi, RpptROIPtr patchRoi, RpptRoiType roiType, rppHandle_t rppHandle); + +#ifdef GPU_SUPPORT +/*! \brief Crop and Patch augmentation on HIP backend for a NCHW/NHWC layout tensor + * \details The crop and patch augmentation crops a ROI from 1st image and patches the cropped region in 2nd image as per the patch co-ordinates + for a batch of RGB(3 channel) / greyscale(1 channel) images with an NHWC/NCHW tensor layout.
+ * - srcPtr1 depth ranges - Rpp8u (0 to 255), Rpp16f (0 to 1), Rpp32f (0 to 1), Rpp8s (-128 to 127). + * - srcPtr2 depth ranges - Rpp8u (0 to 255), Rpp16f (0 to 1), Rpp32f (0 to 1), Rpp8s (-128 to 127). + * - dstPtr depth ranges - Will be same depth as srcPtr. + * \image html img150x150.png Sample Input1 + * \image html img150x150_2.png Sample Input2 + * \image html geometric_augmentations_crop_and_patch_img150x150.png Sample Output + * \param [in] srcPtr1 source tensor1 in HIP memory + * \param [in] srcPtr2 source tensor2 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] 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] cropRoiTensor crop co-ordinates 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] patchRoiTensor patch co-ordinates 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_crop_and_patch_gpu(RppPtr_t srcPtr1, RppPtr_t srcPtr2, RpptDescPtr srcDescPtr, RppPtr_t dstPtr, RpptDescPtr dstDescPtr, RpptROIPtr roiTensorPtrDst, RpptROIPtr cropRoi, RpptROIPtr patchRoi, RpptRoiType roiType, rppHandle_t rppHandle); +#endif // GPU_SUPPORT + /*! \brief Flip voxel augmentation HOST * \details The flip voxel augmentation performs a mask-controlled horizontal/vertical/depth flip on a generic 4D tensor.
Support added for f32 -> f32 and u8 -> u8 dataypes. diff --git a/src/include/hip/rpp_hip_common.hpp b/src/include/hip/rpp_hip_common.hpp index e2e7df9f6..6e014287a 100644 --- a/src/include/hip/rpp_hip_common.hpp +++ b/src/include/hip/rpp_hip_common.hpp @@ -2541,4 +2541,56 @@ __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])); } +// PKD3->PLN3 layout toggle kernel + +template +__global__ void convert_pkd3_pln3_hip_tensor(T *srcPtr, + uint2 srcStridesNH, + T *dstPtr, + uint3 dstStridesNCH, + 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; + + d_float24 dst_f24; + rpp_hip_load24_pkd3_and_unpack_to_float24_pln3(srcPtr + srcIdx, &dst_f24); + rpp_hip_pack_float24_pln3_and_store24_pln3(dstPtr + dstIdx, dstStridesNCH.y, &dst_f24); +} + +// PLN3->PKD3 layout toggle kernel + +template +__global__ void convert_pln3_pkd3_hip_tensor(T *srcPtr, + uint3 srcStridesNCH, + T *dstPtr, + uint2 dstStridesNH, + 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; + + d_float24 dst_f24; + rpp_hip_load24_pln3_and_unpack_to_float24_pkd3(srcPtr + srcIdx, srcStridesNCH.y, &dst_f24); + rpp_hip_pack_float24_pkd3_and_store24_pkd3(dstPtr + dstIdx, &dst_f24); +} + #endif // RPP_HIP_COMMON_H diff --git a/src/modules/cpu/host_tensor_geometric_augmentations.hpp b/src/modules/cpu/host_tensor_geometric_augmentations.hpp index 174b418e8..627e1b70e 100644 --- a/src/modules/cpu/host_tensor_geometric_augmentations.hpp +++ b/src/modules/cpu/host_tensor_geometric_augmentations.hpp @@ -34,6 +34,7 @@ SOFTWARE. #include "kernel/warp_affine.hpp" #include "kernel/phase.hpp" #include "kernel/slice.hpp" +#include "kernel/crop_and_patch.hpp" #include "kernel/flip_voxel.hpp" #endif // HOST_TENSOR_GEOMETRIC_AUGMENTATIONS_HPP diff --git a/src/modules/cpu/kernel/crop_and_patch.hpp b/src/modules/cpu/kernel/crop_and_patch.hpp new file mode 100644 index 000000000..72f31eeb2 --- /dev/null +++ b/src/modules/cpu/kernel/crop_and_patch.hpp @@ -0,0 +1,1540 @@ +/* +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 crop_and_patch_u8_u8_host_tensor(Rpp8u *srcPtr1, + Rpp8u *srcPtr2, + RpptDescPtr srcDescPtr, + Rpp8u *dstPtr, + RpptDescPtr dstDescPtr, + RpptROIPtr roiTensorPtrDst, + RpptROIPtr cropRoiTensor, + RpptROIPtr patchRoiTensor, + 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 = &roiTensorPtrDst[batchCount]; + compute_roi_validation_host(roiPtrInput, &roi, &roiDefault, roiType); + + RpptROIPtr cropRoi = &cropRoiTensor[batchCount]; + RpptROIPtr patchRoi = &patchRoiTensor[batchCount]; + Rpp8u *srcPtr1Image, *srcPtr2Image, *dstPtrImage; + srcPtr1Image = srcPtr1 + batchCount * srcDescPtr->strides.nStride; + srcPtr2Image = srcPtr2 + batchCount * srcDescPtr->strides.nStride; + dstPtrImage = dstPtr + batchCount * dstDescPtr->strides.nStride; + + Rpp32u bufferLength = roi.xywhROI.roiWidth * layoutParams.bufferMultiplier; + Rpp32u cropBufferLength = cropRoi->xywhROI.roiWidth * layoutParams.bufferMultiplier; + Rpp32u patchBufferLength1 = patchRoi->xywhROI.xy.x * layoutParams.bufferMultiplier; + Rpp32u patchBufferLength2 = (roi.xywhROI.roiWidth - (patchRoi->xywhROI.xy.x + cropRoi->xywhROI.roiWidth)) * layoutParams.bufferMultiplier; + Rpp32u vectorIncrement = 48; + Rpp32u vectorIncrementPerChannel = 16; + + Rpp8u *srcPtr1Channel, *srcPtr2Channel, *dstPtrChannel; + srcPtr1Channel = srcPtr1Image + (cropRoi->xywhROI.xy.y * srcDescPtr->strides.hStride) + (cropRoi->xywhROI.xy.x * layoutParams.bufferMultiplier); + srcPtr2Channel = srcPtr2Image + (roi.xywhROI.xy.y * srcDescPtr->strides.hStride) + (roi.xywhROI.xy.x * layoutParams.bufferMultiplier); + dstPtrChannel = dstPtrImage; + if ((srcDescPtr->c == 3) && (srcDescPtr->layout == RpptLayout::NHWC) && (dstDescPtr->layout == RpptLayout::NCHW)) + { + Rpp32u alignedLength = (bufferLength / 48) * 48; + Rpp32u cropAlignedLength = (cropBufferLength / 48) * 48; + Rpp32u patchAlignedLength1 = (patchBufferLength1 / 48) * 48; + Rpp32u patchAlignedLength2 = (patchBufferLength2 / 48) * 48; + + Rpp8u *srcPtr1Row, *srcPtr2Row, *dstPtrRowR, *dstPtrRowG, *dstPtrRowB; + srcPtr1Row = srcPtr1Channel; + srcPtr2Row = srcPtr2Channel; + dstPtrRowR = dstPtrChannel; + dstPtrRowG = dstPtrRowR + dstDescPtr->strides.cStride; + dstPtrRowB = dstPtrRowG + dstDescPtr->strides.cStride; + for(int i = 0; i < roi.xywhROI.roiHeight; i++) + { + Rpp8u *srcPtr1Temp, *srcPtr2Temp, *dstPtrTempR, *dstPtrTempG, *dstPtrTempB; + srcPtr2Temp = srcPtr2Row; + dstPtrTempR = dstPtrRowR; + dstPtrTempG = dstPtrRowG; + dstPtrTempB = dstPtrRowB; + int vectorLoopCount = 0; + if(i >= patchRoi->xywhROI.xy.y && i < (patchRoi->xywhROI.xy.y + cropRoi->xywhROI.roiHeight)) + { +#if __AVX2__ + for(; vectorLoopCount < patchAlignedLength1; vectorLoopCount += vectorIncrement) + { + __m128i px[3]; + rpp_simd_load(rpp_load48_u8pkd3_to_u8pln3, srcPtr2Temp, px); // simd loads + rpp_simd_store(rpp_store48_u8pln3_to_u8pln3, dstPtrTempR, dstPtrTempG, dstPtrTempB, px); // simd stores + srcPtr2Temp += vectorIncrement; + dstPtrTempR += vectorIncrementPerChannel; + dstPtrTempG += vectorIncrementPerChannel; + dstPtrTempB += vectorIncrementPerChannel; + } +#endif + for (; vectorLoopCount < patchBufferLength1; vectorLoopCount += 3) + { + *dstPtrTempR++ = srcPtr2Temp[0]; + *dstPtrTempG++ = srcPtr2Temp[1]; + *dstPtrTempB++ = srcPtr2Temp[2]; + srcPtr2Temp += 3; + } + + vectorLoopCount = 0; + srcPtr1Temp = srcPtr1Row; +#if __AVX2__ + for (; vectorLoopCount < cropAlignedLength; vectorLoopCount += vectorIncrement) + { + __m128i px[3]; + rpp_simd_load(rpp_load48_u8pkd3_to_u8pln3, srcPtr1Temp, px); // simd loads + rpp_simd_store(rpp_store48_u8pln3_to_u8pln3, dstPtrTempR, dstPtrTempG, dstPtrTempB, px); // simd stores + srcPtr1Temp += vectorIncrement; + dstPtrTempR += vectorIncrementPerChannel; + dstPtrTempG += vectorIncrementPerChannel; + dstPtrTempB += vectorIncrementPerChannel; + } +#endif + for (; vectorLoopCount < cropBufferLength; vectorLoopCount += 3) + { + *dstPtrTempR++ = srcPtr1Temp[0]; + *dstPtrTempG++ = srcPtr1Temp[1]; + *dstPtrTempB++ = srcPtr1Temp[2]; + srcPtr1Temp += 3; + } + + vectorLoopCount = 0; + srcPtr2Temp += cropBufferLength; +#if __AVX2__ + for(; vectorLoopCount < patchAlignedLength2; vectorLoopCount += vectorIncrement) + { + __m128i px[3]; + rpp_simd_load(rpp_load48_u8pkd3_to_u8pln3, srcPtr2Temp, px); // simd loads + rpp_simd_store(rpp_store48_u8pln3_to_u8pln3, dstPtrTempR, dstPtrTempG, dstPtrTempB, px); // simd stores + srcPtr2Temp += vectorIncrement; + dstPtrTempR += vectorIncrementPerChannel; + dstPtrTempG += vectorIncrementPerChannel; + dstPtrTempB += vectorIncrementPerChannel; + } +#endif + for (; vectorLoopCount < patchBufferLength2; vectorLoopCount += 3) + { + *dstPtrTempR++ = srcPtr2Temp[0]; + *dstPtrTempG++ = srcPtr2Temp[1]; + *dstPtrTempB++ = srcPtr2Temp[2]; + srcPtr2Temp += 3; + } + srcPtr1Row += srcDescPtr->strides.hStride; + } + else + { +#if __AVX2__ + for(; vectorLoopCount < alignedLength; vectorLoopCount += vectorIncrement) + { + __m128i px[3]; + rpp_simd_load(rpp_load48_u8pkd3_to_u8pln3, srcPtr2Temp, px); // simd loads + rpp_simd_store(rpp_store48_u8pln3_to_u8pln3, dstPtrTempR, dstPtrTempG, dstPtrTempB, px); // simd stores + srcPtr2Temp += vectorIncrement; + dstPtrTempR += vectorIncrementPerChannel; + dstPtrTempG += vectorIncrementPerChannel; + dstPtrTempB += vectorIncrementPerChannel; + } +#endif + for (; vectorLoopCount < bufferLength; vectorLoopCount += 3) + { + *dstPtrTempR++ = srcPtr2Temp[0]; + *dstPtrTempG++ = srcPtr2Temp[1]; + *dstPtrTempB++ = srcPtr2Temp[2]; + srcPtr2Temp += 3; + } + } + srcPtr2Row += srcDescPtr->strides.hStride; + dstPtrRowR += dstDescPtr->strides.hStride; + dstPtrRowG += dstDescPtr->strides.hStride; + dstPtrRowB += dstDescPtr->strides.hStride; + } + } + + else if ((srcDescPtr->c == 3) && (srcDescPtr->layout == RpptLayout::NCHW) && (dstDescPtr->layout == RpptLayout::NHWC)) + { + Rpp32u alignedLength = (bufferLength / 48) * 48; + Rpp32u cropAlignedLength = (cropBufferLength / 48) * 48; + Rpp32u patchAlignedLength1 = (patchBufferLength1 / 48) * 48; + Rpp32u patchAlignedLength2 = (patchBufferLength2 / 48) * 48; + + Rpp8u *srcPtr1RowR, *srcPtr1RowG, *srcPtr1RowB, *srcPtr2RowR, *srcPtr2RowG, *srcPtr2RowB, *dstPtrRow; + srcPtr1RowR = srcPtr1Channel; + srcPtr1RowG = srcPtr1RowR + srcDescPtr->strides.cStride; + srcPtr1RowB = srcPtr1RowG + srcDescPtr->strides.cStride; + srcPtr2RowR = srcPtr2Channel; + srcPtr2RowG = srcPtr2RowR + srcDescPtr->strides.cStride; + srcPtr2RowB = srcPtr2RowG + srcDescPtr->strides.cStride; + dstPtrRow = dstPtrChannel; + for(int i = 0; i < roi.xywhROI.roiHeight; i++) + { + Rpp8u *srcPtr1TempR, *srcPtr1TempG, *srcPtr1TempB, *srcPtr2TempR, *srcPtr2TempG, *srcPtr2TempB, *dstPtrTemp; + srcPtr2TempR = srcPtr2RowR; + srcPtr2TempG = srcPtr2RowG; + srcPtr2TempB = srcPtr2RowB; + dstPtrTemp = dstPtrRow; + int vectorLoopCount = 0; + if(i >= patchRoi->xywhROI.xy.y && i < (patchRoi->xywhROI.xy.y + cropRoi->xywhROI.roiHeight)) + { +#if __AVX2__ + for(; vectorLoopCount < patchAlignedLength1; vectorLoopCount += vectorIncrementPerChannel) + { + __m128i px[3]; + rpp_simd_load(rpp_load48_u8pln3_to_u8pln3, srcPtr2TempR, srcPtr2TempG, srcPtr2TempB, px); // simd loads + rpp_simd_store(rpp_store48_u8pln3_to_u8pkd3, dstPtrTemp, px); // simd stores + srcPtr2TempR += vectorIncrementPerChannel; + srcPtr2TempG += vectorIncrementPerChannel; + srcPtr2TempB += vectorIncrementPerChannel; + dstPtrTemp += vectorIncrement; + } +#endif + for (; vectorLoopCount < patchBufferLength1; vectorLoopCount++) + { + dstPtrTemp[0] = *srcPtr2TempR++; + dstPtrTemp[1] = *srcPtr2TempG++; + dstPtrTemp[2] = *srcPtr2TempB++; + dstPtrTemp += 3; + } + + vectorLoopCount = 0; + srcPtr1TempR = srcPtr1RowR; + srcPtr1TempG = srcPtr1RowG; + srcPtr1TempB = srcPtr1RowB; +#if __AVX2__ + for (; vectorLoopCount < cropAlignedLength; vectorLoopCount += vectorIncrementPerChannel) + { + __m128i px[3]; + rpp_simd_load(rpp_load48_u8pln3_to_u8pln3, srcPtr1TempR, srcPtr1TempG, srcPtr1TempB, px); // simd loads + rpp_simd_store(rpp_store48_u8pln3_to_u8pkd3, dstPtrTemp, px); // simd stores + srcPtr1TempR += vectorIncrementPerChannel; + srcPtr1TempG += vectorIncrementPerChannel; + srcPtr1TempB += vectorIncrementPerChannel; + dstPtrTemp += vectorIncrement; + } +#endif + for (; vectorLoopCount < cropBufferLength; vectorLoopCount++) + { + dstPtrTemp[0] = *srcPtr1TempR++; + dstPtrTemp[1] = *srcPtr1TempG++; + dstPtrTemp[2] = *srcPtr1TempB++; + dstPtrTemp += 3; + } + + vectorLoopCount = 0; + srcPtr2TempR += cropBufferLength; + srcPtr2TempG += cropBufferLength; + srcPtr2TempB += cropBufferLength; +#if __AVX2__ + for(; vectorLoopCount < patchAlignedLength2; vectorLoopCount += vectorIncrementPerChannel) + { + __m128i px[3]; + rpp_simd_load(rpp_load48_u8pln3_to_u8pln3, srcPtr2TempR, srcPtr2TempG, srcPtr2TempB, px); // simd loads + rpp_simd_store(rpp_store48_u8pln3_to_u8pkd3, dstPtrTemp, px); // simd stores + srcPtr2TempR += vectorIncrementPerChannel; + srcPtr2TempG += vectorIncrementPerChannel; + srcPtr2TempB += vectorIncrementPerChannel; + dstPtrTemp += vectorIncrement; + } +#endif + for (; vectorLoopCount < patchBufferLength2; vectorLoopCount++) + { + dstPtrTemp[0] = *srcPtr2TempR++; + dstPtrTemp[1] = *srcPtr2TempG++; + dstPtrTemp[2] = *srcPtr2TempB++; + dstPtrTemp += 3; + } + srcPtr1RowR += srcDescPtr->strides.hStride; + srcPtr1RowG += srcDescPtr->strides.hStride; + srcPtr1RowB += srcDescPtr->strides.hStride; + } + else + { +#if __AVX2__ + for(; vectorLoopCount < alignedLength; vectorLoopCount += vectorIncrementPerChannel) + { + __m128i px[3]; + rpp_simd_load(rpp_load48_u8pln3_to_u8pln3, srcPtr2TempR, srcPtr2TempG, srcPtr2TempB, px); // simd loads + rpp_simd_store(rpp_store48_u8pln3_to_u8pkd3, dstPtrTemp, px); // simd stores + srcPtr2TempR += vectorIncrementPerChannel; + srcPtr2TempG += vectorIncrementPerChannel; + srcPtr2TempB += vectorIncrementPerChannel; + dstPtrTemp += vectorIncrement; + } +#endif + for (; vectorLoopCount < bufferLength; vectorLoopCount++) + { + dstPtrTemp[0] = *srcPtr2TempR++; + dstPtrTemp[1] = *srcPtr2TempG++; + dstPtrTemp[2] = *srcPtr2TempB++; + dstPtrTemp += 3; + } + } + srcPtr2RowR += srcDescPtr->strides.hStride; + srcPtr2RowG += srcDescPtr->strides.hStride; + srcPtr2RowB += srcDescPtr->strides.hStride; + dstPtrRow += dstDescPtr->strides.hStride; + } + } + + else if ((srcDescPtr->c == 3) && (srcDescPtr->layout == RpptLayout::NHWC) && (dstDescPtr->layout == RpptLayout::NHWC)) + { + Rpp8u *srcPtr1Row, *srcPtr2Row, *dstPtrRow; + srcPtr1Row = srcPtr1Channel; + srcPtr2Row = srcPtr2Channel; + dstPtrRow = dstPtrChannel; + for(int i = 0; i < roi.xywhROI.roiHeight; i++) + { + Rpp8u *dstPtrRowTemp = dstPtrRow; + Rpp8u *srcPtr1RowTemp = srcPtr1Row; + Rpp8u *srcPtr2RowTemp = srcPtr2Row; + if(i >= patchRoi->xywhROI.xy.y && i < (patchRoi->xywhROI.xy.y + cropRoi->xywhROI.roiHeight)) + { + memcpy(dstPtrRowTemp, srcPtr2RowTemp, patchBufferLength1); + srcPtr2RowTemp += patchBufferLength1; + dstPtrRowTemp += patchBufferLength1; + memcpy(dstPtrRowTemp, srcPtr1RowTemp, cropBufferLength); + srcPtr2RowTemp += cropBufferLength; + dstPtrRowTemp += cropBufferLength; + memcpy(dstPtrRowTemp, srcPtr2RowTemp, patchBufferLength2); + srcPtr1Row += srcDescPtr->strides.hStride; + } + else + { + memcpy(dstPtrRowTemp, srcPtr2RowTemp, bufferLength); + } + srcPtr2Row += srcDescPtr->strides.hStride; + dstPtrRow += dstDescPtr->strides.hStride; + } + } + else + { + for(int c = 0; c < layoutParams.channelParam; c++) + { + Rpp8u *srcPtr1Row, *srcPtr2Row, *dstPtrRow; + srcPtr1Row = srcPtr1Channel; + srcPtr2Row = srcPtr2Channel; + dstPtrRow = dstPtrChannel; + for(int i = 0; i < roi.xywhROI.roiHeight; i++) + { + Rpp8u *dstPtrRowTemp = dstPtrRow; + Rpp8u *srcPtr1RowTemp = srcPtr1Row; + Rpp8u *srcPtr2RowTemp = srcPtr2Row; + if(i >= patchRoi->xywhROI.xy.y && i < (patchRoi->xywhROI.xy.y + cropRoi->xywhROI.roiHeight)) + { + memcpy(dstPtrRowTemp, srcPtr2RowTemp, patchBufferLength1); + srcPtr2RowTemp += patchBufferLength1; + dstPtrRowTemp += patchBufferLength1; + memcpy(dstPtrRowTemp, srcPtr1RowTemp, cropBufferLength); + srcPtr2RowTemp += cropBufferLength; + dstPtrRowTemp += cropBufferLength; + memcpy(dstPtrRowTemp, srcPtr2RowTemp, patchBufferLength2); + srcPtr1Row += srcDescPtr->strides.hStride; + } + else + { + memcpy(dstPtrRow, srcPtr2RowTemp, bufferLength); + } + srcPtr2Row += srcDescPtr->strides.hStride; + dstPtrRow += dstDescPtr->strides.hStride; + } + srcPtr1Channel += srcDescPtr->strides.cStride; + srcPtr2Channel += srcDescPtr->strides.cStride; + dstPtrChannel += dstDescPtr->strides.cStride; + } + } + } + + return RPP_SUCCESS; +} + +RppStatus crop_and_patch_f32_f32_host_tensor(Rpp32f *srcPtr1, + Rpp32f *srcPtr2, + RpptDescPtr srcDescPtr, + Rpp32f *dstPtr, + RpptDescPtr dstDescPtr, + RpptROIPtr roiTensorPtrDst, + RpptROIPtr cropRoiTensor, + RpptROIPtr patchRoiTensor, + 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 = &roiTensorPtrDst[batchCount]; + compute_roi_validation_host(roiPtrInput, &roi, &roiDefault, roiType); + + RpptROIPtr cropRoi = &cropRoiTensor[batchCount]; + RpptROIPtr patchRoi = &patchRoiTensor[batchCount]; + Rpp32f *srcPtr1Image, *srcPtr2Image, *dstPtrImage; + srcPtr1Image = srcPtr1 + batchCount * srcDescPtr->strides.nStride; + srcPtr2Image = srcPtr2 + batchCount * srcDescPtr->strides.nStride; + dstPtrImage = dstPtr + batchCount * dstDescPtr->strides.nStride; + Rpp32u bufferLength = roi.xywhROI.roiWidth * layoutParams.bufferMultiplier; + Rpp32u cropBufferLength = cropRoi->xywhROI.roiWidth * layoutParams.bufferMultiplier; + Rpp32u patchBufferLength1 = patchRoi->xywhROI.xy.x * layoutParams.bufferMultiplier; + Rpp32u patchBufferLength2 = (roi.xywhROI.roiWidth - (patchRoi->xywhROI.xy.x + cropRoi->xywhROI.roiWidth)) * layoutParams.bufferMultiplier; + Rpp32u vectorIncrement = 12; + Rpp32u vectorIncrementPerChannel = 4; + + Rpp32f *srcPtr1Channel, *srcPtr2Channel, *dstPtrChannel; + srcPtr1Channel = srcPtr1Image + (cropRoi->xywhROI.xy.y * srcDescPtr->strides.hStride) + (cropRoi->xywhROI.xy.x * layoutParams.bufferMultiplier); + srcPtr2Channel = srcPtr2Image + (roi.xywhROI.xy.y * srcDescPtr->strides.hStride) + (roi.xywhROI.xy.x * layoutParams.bufferMultiplier); + dstPtrChannel = dstPtrImage; + if ((srcDescPtr->c == 3) && (srcDescPtr->layout == RpptLayout::NHWC) && (dstDescPtr->layout == RpptLayout::NCHW)) + { + Rpp32u alignedLength = (bufferLength / 12) * 12; + Rpp32u cropAlignedLength = (cropBufferLength / 12) * 12; + Rpp32u patchAlignedLength1 = (patchBufferLength1 / 12) * 12; + Rpp32u patchAlignedLength2 = (patchBufferLength2 / 12) * 12; + + Rpp32f *srcPtr1Row, *srcPtr2Row, *dstPtrRowR, *dstPtrRowG, *dstPtrRowB; + srcPtr1Row = srcPtr1Channel; + srcPtr2Row = srcPtr2Channel; + dstPtrRowR = dstPtrChannel; + dstPtrRowG = dstPtrRowR + dstDescPtr->strides.cStride; + dstPtrRowB = dstPtrRowG + dstDescPtr->strides.cStride; + for(int i = 0; i < roi.xywhROI.roiHeight; i++) + { + Rpp32f *srcPtr1Temp, *srcPtr2Temp, *dstPtrTempR, *dstPtrTempG, *dstPtrTempB; + srcPtr2Temp = srcPtr2Row; + dstPtrTempR = dstPtrRowR; + dstPtrTempG = dstPtrRowG; + dstPtrTempB = dstPtrRowB; + int vectorLoopCount = 0; + if(i >= patchRoi->xywhROI.xy.y && i < (patchRoi->xywhROI.xy.y + cropRoi->xywhROI.roiHeight)) + { +#if __AVX2__ + for(; vectorLoopCount < patchAlignedLength1; vectorLoopCount += vectorIncrement) + { + __m128 p[4]; + rpp_simd_load(rpp_load12_f32pkd3_to_f32pln3, srcPtr2Temp, p); // simd loads + rpp_simd_store(rpp_store12_f32pln3_to_f32pln3, dstPtrTempR, dstPtrTempG, dstPtrTempB, p); // simd stores + srcPtr2Temp += vectorIncrement; + dstPtrTempR += vectorIncrementPerChannel; + dstPtrTempG += vectorIncrementPerChannel; + dstPtrTempB += vectorIncrementPerChannel; + } +#endif + for (; vectorLoopCount < patchBufferLength1; vectorLoopCount += 3) + { + *dstPtrTempR++ = srcPtr2Temp[0]; + *dstPtrTempG++ = srcPtr2Temp[1]; + *dstPtrTempB++ = srcPtr2Temp[2]; + srcPtr2Temp += 3; + } + + vectorLoopCount = 0; + srcPtr1Temp = srcPtr1Row; +#if __AVX2__ + for (; vectorLoopCount < cropAlignedLength; vectorLoopCount += vectorIncrement) + { + __m128 p[4]; + rpp_simd_load(rpp_load12_f32pkd3_to_f32pln3, srcPtr1Temp, p); // simd loads + rpp_simd_store(rpp_store12_f32pln3_to_f32pln3, dstPtrTempR, dstPtrTempG, dstPtrTempB, p); // simd stores + srcPtr1Temp += vectorIncrement; + dstPtrTempR += vectorIncrementPerChannel; + dstPtrTempG += vectorIncrementPerChannel; + dstPtrTempB += vectorIncrementPerChannel; + } +#endif + for (; vectorLoopCount < cropBufferLength; vectorLoopCount += 3) + { + *dstPtrTempR++ = srcPtr1Temp[0]; + *dstPtrTempG++ = srcPtr1Temp[1]; + *dstPtrTempB++ = srcPtr1Temp[2]; + srcPtr1Temp += 3; + } + + vectorLoopCount = 0; + srcPtr2Temp += cropBufferLength; +#if __AVX2__ + for(; vectorLoopCount < patchAlignedLength2; vectorLoopCount += vectorIncrement) + { + __m128 p[4]; + rpp_simd_load(rpp_load12_f32pkd3_to_f32pln3, srcPtr2Temp, p); // simd loads + rpp_simd_store(rpp_store12_f32pln3_to_f32pln3, dstPtrTempR, dstPtrTempG, dstPtrTempB, p); // simd stores + srcPtr2Temp += vectorIncrement; + dstPtrTempR += vectorIncrementPerChannel; + dstPtrTempG += vectorIncrementPerChannel; + dstPtrTempB += vectorIncrementPerChannel; + } +#endif + for (; vectorLoopCount < patchBufferLength2; vectorLoopCount += 3) + { + *dstPtrTempR++ = srcPtr2Temp[0]; + *dstPtrTempG++ = srcPtr2Temp[1]; + *dstPtrTempB++ = srcPtr2Temp[2]; + srcPtr2Temp += 3; + } + srcPtr1Row += srcDescPtr->strides.hStride; + } + else + { +#if __AVX2__ + for(; vectorLoopCount < alignedLength; vectorLoopCount += vectorIncrement) + { + __m128 p[4]; + rpp_simd_load(rpp_load12_f32pkd3_to_f32pln3, srcPtr2Temp, p); // simd loads + rpp_simd_store(rpp_store12_f32pln3_to_f32pln3, dstPtrTempR, dstPtrTempG, dstPtrTempB, p); // simd stores + srcPtr2Temp += vectorIncrement; + dstPtrTempR += vectorIncrementPerChannel; + dstPtrTempG += vectorIncrementPerChannel; + dstPtrTempB += vectorIncrementPerChannel; + } +#endif + for (; vectorLoopCount < bufferLength; vectorLoopCount += 3) + { + *dstPtrTempR++ = srcPtr2Temp[0]; + *dstPtrTempG++ = srcPtr2Temp[1]; + *dstPtrTempB++ = srcPtr2Temp[2]; + srcPtr2Temp += 3; + } + } + srcPtr2Row += srcDescPtr->strides.hStride; + dstPtrRowR += dstDescPtr->strides.hStride; + dstPtrRowG += dstDescPtr->strides.hStride; + dstPtrRowB += dstDescPtr->strides.hStride; + } + } + else if ((srcDescPtr->c == 3) && (srcDescPtr->layout == RpptLayout::NCHW) && (dstDescPtr->layout == RpptLayout::NHWC)) + { + Rpp32u alignedLength = (bufferLength / 12) * 12; + Rpp32u cropAlignedLength = (cropBufferLength / 12) * 12; + Rpp32u patchAlignedLength1 = (patchBufferLength1 / 12) * 12; + Rpp32u patchAlignedLength2 = (patchBufferLength2 / 12) * 12; + + Rpp32f *srcPtr1RowR, *srcPtr1RowG, *srcPtr1RowB, *srcPtr2RowR, *srcPtr2RowG, *srcPtr2RowB, *dstPtrRow; + srcPtr1RowR = srcPtr1Channel; + srcPtr1RowG = srcPtr1RowR + srcDescPtr->strides.cStride; + srcPtr1RowB = srcPtr1RowG + srcDescPtr->strides.cStride; + srcPtr2RowR = srcPtr2Channel; + srcPtr2RowG = srcPtr2RowR + srcDescPtr->strides.cStride; + srcPtr2RowB = srcPtr2RowG + srcDescPtr->strides.cStride; + dstPtrRow = dstPtrChannel; + for(int i = 0; i < roi.xywhROI.roiHeight; i++) + { + Rpp32f *srcPtr1TempR, *srcPtr1TempG, *srcPtr1TempB, *srcPtr2TempR, *srcPtr2TempG, *srcPtr2TempB, *dstPtrTemp; + srcPtr2TempR = srcPtr2RowR; + srcPtr2TempG = srcPtr2RowG; + srcPtr2TempB = srcPtr2RowB; + dstPtrTemp = dstPtrRow; + int vectorLoopCount = 0; + if(i >= patchRoi->xywhROI.xy.y && i < (patchRoi->xywhROI.xy.y + cropRoi->xywhROI.roiHeight)) + { +#if __AVX2__ + for(; vectorLoopCount < patchAlignedLength1; vectorLoopCount+=4) + { + __m128 p[4]; + rpp_simd_load(rpp_load12_f32pln3_to_f32pln3, srcPtr2TempR, srcPtr2TempG, srcPtr2TempB, p); // simd loads + rpp_simd_store(rpp_store12_f32pln3_to_f32pkd3, dstPtrTemp, p); // simd stores + srcPtr2TempR += vectorIncrementPerChannel; + srcPtr2TempG += vectorIncrementPerChannel; + srcPtr2TempB += vectorIncrementPerChannel; + dstPtrTemp += vectorIncrement; + } +#endif + for (; vectorLoopCount < patchBufferLength1; vectorLoopCount++) + { + dstPtrTemp[0] = *srcPtr2TempR++; + dstPtrTemp[1] = *srcPtr2TempG++; + dstPtrTemp[2] = *srcPtr2TempB++; + dstPtrTemp += 3; + } + + vectorLoopCount = 0; + srcPtr1TempR = srcPtr1RowR; + srcPtr1TempG = srcPtr1RowG; + srcPtr1TempB = srcPtr1RowB; +#if __AVX2__ + for (; vectorLoopCount < cropAlignedLength; vectorLoopCount+=4) + { + __m128 p[4]; + rpp_simd_load(rpp_load12_f32pln3_to_f32pln3, srcPtr1TempR, srcPtr1TempG, srcPtr1TempB, p); // simd loads + rpp_simd_store(rpp_store12_f32pln3_to_f32pkd3, dstPtrTemp, p); // simd stores + srcPtr1TempR += vectorIncrementPerChannel; + srcPtr1TempG += vectorIncrementPerChannel; + srcPtr1TempB += vectorIncrementPerChannel; + dstPtrTemp += vectorIncrement; + } +#endif + for (; vectorLoopCount < cropBufferLength; vectorLoopCount++) + { + dstPtrTemp[0] = *srcPtr1TempR++; + dstPtrTemp[1] = *srcPtr1TempG++; + dstPtrTemp[2] = *srcPtr1TempB++; + dstPtrTemp += 3; + } + + vectorLoopCount = 0; + srcPtr2TempR += cropBufferLength; + srcPtr2TempG += cropBufferLength; + srcPtr2TempB += cropBufferLength; +#if __AVX2__ + for(; vectorLoopCount < patchAlignedLength2; vectorLoopCount+=4) + { + __m128 p[4]; + rpp_simd_load(rpp_load12_f32pln3_to_f32pln3, srcPtr2TempR, srcPtr2TempG, srcPtr2TempB, p); // simd loads + rpp_simd_store(rpp_store12_f32pln3_to_f32pkd3, dstPtrTemp, p); // simd stores + srcPtr2TempR += vectorIncrementPerChannel; + srcPtr2TempG += vectorIncrementPerChannel; + srcPtr2TempB += vectorIncrementPerChannel; + dstPtrTemp += vectorIncrement; + } +#endif + for (; vectorLoopCount < patchBufferLength2; vectorLoopCount++) + { + dstPtrTemp[0] = *srcPtr2TempR++; + dstPtrTemp[1] = *srcPtr2TempG++; + dstPtrTemp[2] = *srcPtr2TempB++; + dstPtrTemp += 3; + } + srcPtr1RowR += srcDescPtr->strides.hStride; + srcPtr1RowG += srcDescPtr->strides.hStride; + srcPtr1RowB += srcDescPtr->strides.hStride; + } + else + { +#if __AVX2__ + for(; vectorLoopCount < alignedLength; vectorLoopCount+=4) + { + __m128 p[4]; + rpp_simd_load(rpp_load12_f32pln3_to_f32pln3, srcPtr2TempR, srcPtr2TempG, srcPtr2TempB, p); // simd loads + rpp_simd_store(rpp_store12_f32pln3_to_f32pkd3, dstPtrTemp, p); // simd stores + srcPtr2TempR += vectorIncrementPerChannel; + srcPtr2TempG += vectorIncrementPerChannel; + srcPtr2TempB += vectorIncrementPerChannel; + dstPtrTemp += vectorIncrement; + } +#endif + for (; vectorLoopCount < bufferLength; vectorLoopCount++) + { + dstPtrTemp[0] = *srcPtr2TempR++; + dstPtrTemp[1] = *srcPtr2TempG++; + dstPtrTemp[2] = *srcPtr2TempB++; + dstPtrTemp += 3; + } + } + srcPtr2RowR += srcDescPtr->strides.hStride; + srcPtr2RowG += srcDescPtr->strides.hStride; + srcPtr2RowB += srcDescPtr->strides.hStride; + dstPtrRow += dstDescPtr->strides.hStride; + } + } + else if ((srcDescPtr->c == 3) && (srcDescPtr->layout == RpptLayout::NHWC) && (dstDescPtr->layout == RpptLayout::NHWC)) + { + Rpp32u bufferLengthInBytes = bufferLength * sizeof(Rpp32f); + Rpp32u cropBufferLengthInBytes = cropBufferLength * sizeof(Rpp32f); + Rpp32u patchBufferLength1InBytes = patchBufferLength1 * sizeof(Rpp32f); + Rpp32u patchBufferLength2InBytes = patchBufferLength2 * sizeof(Rpp32f); + + Rpp32f *srcPtr1Row, *srcPtr2Row, *dstPtrRow; + srcPtr1Row = srcPtr1Channel; + srcPtr2Row = srcPtr2Channel; + dstPtrRow = dstPtrChannel; + for(int i = 0; i < roi.xywhROI.roiHeight; i++) + { + Rpp32f *dstPtrRowTemp = dstPtrRow; + Rpp32f *srcPtr1RowTemp = srcPtr1Row; + Rpp32f *srcPtr2RowTemp = srcPtr2Row; + if(i >= patchRoi->xywhROI.xy.y && i < (patchRoi->xywhROI.xy.y + cropRoi->xywhROI.roiHeight)) + { + memcpy(dstPtrRowTemp, srcPtr2RowTemp, patchBufferLength1InBytes); + srcPtr2RowTemp += patchBufferLength1; + dstPtrRowTemp += patchBufferLength1; + memcpy(dstPtrRowTemp, srcPtr1RowTemp, cropBufferLengthInBytes); + srcPtr2RowTemp += cropBufferLength; + dstPtrRowTemp += cropBufferLength; + memcpy(dstPtrRowTemp, srcPtr2RowTemp, patchBufferLength2InBytes); + srcPtr1Row += srcDescPtr->strides.hStride; + } + else + { + memcpy(dstPtrRowTemp, srcPtr2RowTemp, bufferLengthInBytes); + } + srcPtr2Row += srcDescPtr->strides.hStride; + dstPtrRow += dstDescPtr->strides.hStride; + } + } + else + { + Rpp32u bufferLengthInBytes = bufferLength * sizeof(Rpp32f); + Rpp32u cropBufferLengthInBytes = cropBufferLength * sizeof(Rpp32f); + Rpp32u patchBufferLength1InBytes = patchBufferLength1 * sizeof(Rpp32f); + Rpp32u patchBufferLength2InBytes = patchBufferLength2 * sizeof(Rpp32f); + for(int c = 0; c < layoutParams.channelParam; c++) + { + Rpp32f *srcPtr1Row, *srcPtr2Row, *dstPtrRow; + srcPtr1Row = srcPtr1Channel; + srcPtr2Row = srcPtr2Channel; + dstPtrRow = dstPtrChannel; + for(int i = 0; i < roi.xywhROI.roiHeight; i++) + { + Rpp32f *dstPtrRowTemp = dstPtrRow; + Rpp32f *srcPtr1RowTemp = srcPtr1Row; + Rpp32f *srcPtr2RowTemp = srcPtr2Row; + if(i >= patchRoi->xywhROI.xy.y && i < (patchRoi->xywhROI.xy.y + cropRoi->xywhROI.roiHeight)) + { + memcpy(dstPtrRowTemp, srcPtr2RowTemp, patchBufferLength1InBytes); + srcPtr2RowTemp += patchBufferLength1; + dstPtrRowTemp += patchBufferLength1; + memcpy(dstPtrRowTemp, srcPtr1RowTemp, cropBufferLengthInBytes); + srcPtr2RowTemp += cropBufferLength; + dstPtrRowTemp += cropBufferLength; + memcpy(dstPtrRowTemp, srcPtr2RowTemp, patchBufferLength2InBytes); + srcPtr1Row += srcDescPtr->strides.hStride; + } + else + { + memcpy(dstPtrRow, srcPtr2RowTemp, bufferLengthInBytes); + } + srcPtr2Row += srcDescPtr->strides.hStride; + dstPtrRow += dstDescPtr->strides.hStride; + } + srcPtr1Channel += srcDescPtr->strides.cStride; + srcPtr2Channel += srcDescPtr->strides.cStride; + dstPtrChannel += dstDescPtr->strides.cStride; + } + } + } + + return RPP_SUCCESS; +} + +RppStatus crop_and_patch_f16_f16_host_tensor(Rpp16f *srcPtr1, + Rpp16f *srcPtr2, + RpptDescPtr srcDescPtr, + Rpp16f *dstPtr, + RpptDescPtr dstDescPtr, + RpptROIPtr roiTensorPtrDst, + RpptROIPtr cropRoiTensor, + RpptROIPtr patchRoiTensor, + 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 = &roiTensorPtrDst[batchCount]; + compute_roi_validation_host(roiPtrInput, &roi, &roiDefault, roiType); + + RpptROIPtr cropRoi = &cropRoiTensor[batchCount]; + RpptROIPtr patchRoi = &patchRoiTensor[batchCount]; + Rpp16f *srcPtr1Image, *srcPtr2Image, *dstPtrImage; + srcPtr1Image = srcPtr1 + batchCount * srcDescPtr->strides.nStride; + srcPtr2Image = srcPtr2 + batchCount * srcDescPtr->strides.nStride; + dstPtrImage = dstPtr + batchCount * dstDescPtr->strides.nStride; + + Rpp32u bufferLength = roi.xywhROI.roiWidth * layoutParams.bufferMultiplier; + Rpp32u cropBufferLength = cropRoi->xywhROI.roiWidth * layoutParams.bufferMultiplier; + Rpp32u patchBufferLength1 = patchRoi->xywhROI.xy.x * layoutParams.bufferMultiplier; + Rpp32u patchBufferLength2 = (roi.xywhROI.roiWidth - (patchRoi->xywhROI.xy.x + cropRoi->xywhROI.roiWidth)) * layoutParams.bufferMultiplier; + Rpp32u vectorIncrement = 12; + Rpp32u vectorIncrementPerChannel = 4; + + Rpp16f *srcPtr1Channel, *srcPtr2Channel, *dstPtrChannel; + srcPtr1Channel = srcPtr1Image + (cropRoi->xywhROI.xy.y * srcDescPtr->strides.hStride) + (cropRoi->xywhROI.xy.x * layoutParams.bufferMultiplier); + srcPtr2Channel = srcPtr2Image + (roi.xywhROI.xy.y * srcDescPtr->strides.hStride) + (roi.xywhROI.xy.x * layoutParams.bufferMultiplier); + dstPtrChannel = dstPtrImage; + if ((srcDescPtr->c == 3) && (srcDescPtr->layout == RpptLayout::NHWC) && (dstDescPtr->layout == RpptLayout::NCHW)) + { + Rpp32u alignedLength = (bufferLength / 12) * 12; + Rpp32u cropAlignedLength = (cropBufferLength / 12) * 12; + Rpp32u patchAlignedLength1 = (patchBufferLength1 / 12) * 12; + Rpp32u patchAlignedLength2 = (patchBufferLength2 / 12) * 12; + + Rpp16f *srcPtr1Row, *srcPtr2Row, *dstPtrRowR, *dstPtrRowG, *dstPtrRowB; + srcPtr1Row = srcPtr1Channel; + srcPtr2Row = srcPtr2Channel; + dstPtrRowR = dstPtrChannel; + dstPtrRowG = dstPtrRowR + dstDescPtr->strides.cStride; + dstPtrRowB = dstPtrRowG + dstDescPtr->strides.cStride; + for(int i = 0; i < roi.xywhROI.roiHeight; i++) + { + Rpp16f *srcPtr1Temp, *srcPtr2Temp, *dstPtrTempR, *dstPtrTempG, *dstPtrTempB; + srcPtr2Temp = srcPtr2Row; + dstPtrTempR = dstPtrRowR; + dstPtrTempG = dstPtrRowG; + dstPtrTempB = dstPtrRowB; + int vectorLoopCount = 0; + if(i >= patchRoi->xywhROI.xy.y && i < (patchRoi->xywhROI.xy.y + cropRoi->xywhROI.roiHeight)) + { +#if __AVX2__ + for(; vectorLoopCount < patchAlignedLength1; vectorLoopCount += vectorIncrement) + { + Rpp32f srcPtrTemp_ps[12], dstPtrTemp_ps[12]; + for(int cnt = 0; cnt < 12; cnt++) + *(srcPtrTemp_ps + cnt) = (Rpp32f) *(srcPtr2Temp + cnt); + + __m128 p[4]; + rpp_simd_load(rpp_load12_f32pkd3_to_f32pln3, srcPtrTemp_ps, p); // simd loads + rpp_simd_store(rpp_store12_f32pln3_to_f32pln3, dstPtrTemp_ps, dstPtrTemp_ps + 4, dstPtrTemp_ps + 8, p); // simd stores + for(int cnt = 0; cnt < 4; cnt++) + { + *(dstPtrTempR + cnt) = (Rpp16f) *(dstPtrTemp_ps + cnt); + *(dstPtrTempG + cnt) = (Rpp16f) *(dstPtrTemp_ps + 4 + cnt); + *(dstPtrTempB + cnt) = (Rpp16f) *(dstPtrTemp_ps + 8 + cnt); + } + srcPtr2Temp += vectorIncrement; + dstPtrTempR += vectorIncrementPerChannel; + dstPtrTempG += vectorIncrementPerChannel; + dstPtrTempB += vectorIncrementPerChannel; + } +#endif + for (; vectorLoopCount < patchBufferLength1; vectorLoopCount += 3) + { + *dstPtrTempR++ = srcPtr2Temp[0]; + *dstPtrTempG++ = srcPtr2Temp[1]; + *dstPtrTempB++ = srcPtr2Temp[2]; + srcPtr2Temp += 3; + } + vectorLoopCount = 0; + srcPtr1Temp = srcPtr1Row; +#if __AVX2__ + for (; vectorLoopCount < cropAlignedLength; vectorLoopCount += vectorIncrement) + { + Rpp32f srcPtrTemp_ps[12], dstPtrTemp_ps[12]; + for(int cnt = 0; cnt < 12; cnt++) + *(srcPtrTemp_ps + cnt) = (Rpp32f) *(srcPtr1Temp + cnt); + + __m128 p[4]; + rpp_simd_load(rpp_load12_f32pkd3_to_f32pln3, srcPtrTemp_ps, p); // simd loads + rpp_simd_store(rpp_store12_f32pln3_to_f32pln3, dstPtrTemp_ps, dstPtrTemp_ps + 4, dstPtrTemp_ps + 8, p); // simd stores + for(int cnt = 0; cnt < 4; cnt++) + { + *(dstPtrTempR + cnt) = (Rpp16f) *(dstPtrTemp_ps + cnt); + *(dstPtrTempG + cnt) = (Rpp16f) *(dstPtrTemp_ps + 4 + cnt); + *(dstPtrTempB + cnt) = (Rpp16f) *(dstPtrTemp_ps + 8 + cnt); + } + srcPtr1Temp += vectorIncrement; + dstPtrTempR += vectorIncrementPerChannel; + dstPtrTempG += vectorIncrementPerChannel; + dstPtrTempB += vectorIncrementPerChannel; + } +#endif + for (; vectorLoopCount < cropBufferLength; vectorLoopCount += 3) + { + *dstPtrTempR++ = srcPtr1Temp[0]; + *dstPtrTempG++ = srcPtr1Temp[1]; + *dstPtrTempB++ = srcPtr1Temp[2]; + srcPtr1Temp += 3; + } + + vectorLoopCount = 0; + srcPtr2Temp += cropBufferLength; +#if __AVX2__ + for(; vectorLoopCount < patchAlignedLength2; vectorLoopCount += vectorIncrement) + { + Rpp32f srcPtrTemp_ps[12], dstPtrTemp_ps[12]; + for(int cnt = 0; cnt < 12; cnt++) + *(srcPtrTemp_ps + cnt) = (Rpp32f) *(srcPtr2Temp + cnt); + + __m128 p[4]; + rpp_simd_load(rpp_load12_f32pkd3_to_f32pln3, srcPtrTemp_ps, p); // simd loads + rpp_simd_store(rpp_store12_f32pln3_to_f32pln3, dstPtrTemp_ps, dstPtrTemp_ps + 4, dstPtrTemp_ps + 8, p); // simd stores + for(int cnt = 0; cnt < 4; cnt++) + { + *(dstPtrTempR + cnt) = (Rpp16f) *(dstPtrTemp_ps + cnt); + *(dstPtrTempG + cnt) = (Rpp16f) *(dstPtrTemp_ps + 4 + cnt); + *(dstPtrTempB + cnt) = (Rpp16f) *(dstPtrTemp_ps + 8 + cnt); + } + srcPtr2Temp += vectorIncrement; + dstPtrTempR += vectorIncrementPerChannel; + dstPtrTempG += vectorIncrementPerChannel; + dstPtrTempB += vectorIncrementPerChannel; + } +#endif + for (; vectorLoopCount < patchBufferLength2; vectorLoopCount += 3) + { + *dstPtrTempR++ = srcPtr2Temp[0]; + *dstPtrTempG++ = srcPtr2Temp[1]; + *dstPtrTempB++ = srcPtr2Temp[2]; + srcPtr2Temp += 3; + } + srcPtr1Row += srcDescPtr->strides.hStride; + } + else + { +#if __AVX2__ + for(; vectorLoopCount < alignedLength; vectorLoopCount += vectorIncrement) + { + Rpp32f srcPtrTemp_ps[12], dstPtrTemp_ps[12]; + for(int cnt = 0; cnt < 12; cnt++) + *(srcPtrTemp_ps + cnt) = (Rpp32f) *(srcPtr2Temp + cnt); + + __m128 p[4]; + rpp_simd_load(rpp_load12_f32pkd3_to_f32pln3, srcPtrTemp_ps, p); // simd loads + rpp_simd_store(rpp_store12_f32pln3_to_f32pln3, dstPtrTemp_ps, dstPtrTemp_ps + 4, dstPtrTemp_ps + 8, p); // simd stores + for(int cnt = 0; cnt < 4; cnt++) + { + *(dstPtrTempR + cnt) = (Rpp16f) *(dstPtrTemp_ps + cnt); + *(dstPtrTempG + cnt) = (Rpp16f) *(dstPtrTemp_ps + 4 + cnt); + *(dstPtrTempB + cnt) = (Rpp16f) *(dstPtrTemp_ps + 8 + cnt); + } + srcPtr2Temp += vectorIncrement; + dstPtrTempR += vectorIncrementPerChannel; + dstPtrTempG += vectorIncrementPerChannel; + dstPtrTempB += vectorIncrementPerChannel; + } +#endif + for (; vectorLoopCount < bufferLength; vectorLoopCount += 3) + { + *dstPtrTempR++ = srcPtr2Temp[0]; + *dstPtrTempG++ = srcPtr2Temp[1]; + *dstPtrTempB++ = srcPtr2Temp[2]; + srcPtr2Temp += 3; + } + } + srcPtr2Row += srcDescPtr->strides.hStride; + dstPtrRowR += dstDescPtr->strides.hStride; + dstPtrRowG += dstDescPtr->strides.hStride; + dstPtrRowB += dstDescPtr->strides.hStride; + } + } + else if ((srcDescPtr->c == 3) && (srcDescPtr->layout == RpptLayout::NCHW) && (dstDescPtr->layout == RpptLayout::NHWC)) + { + Rpp32u alignedLength = (bufferLength / 12) * 12; + Rpp32u cropAlignedLength = (cropBufferLength / 12) * 12; + Rpp32u patchAlignedLength1 = (patchBufferLength1 / 12) * 12; + Rpp32u patchAlignedLength2 = (patchBufferLength2 / 12) * 12; + + Rpp16f *srcPtr1RowR, *srcPtr1RowG, *srcPtr1RowB, *srcPtr2RowR, *srcPtr2RowG, *srcPtr2RowB, *dstPtrRow; + srcPtr1RowR = srcPtr1Channel; + srcPtr1RowG = srcPtr1RowR + srcDescPtr->strides.cStride; + srcPtr1RowB = srcPtr1RowG + srcDescPtr->strides.cStride; + srcPtr2RowR = srcPtr2Channel; + srcPtr2RowG = srcPtr2RowR + srcDescPtr->strides.cStride; + srcPtr2RowB = srcPtr2RowG + srcDescPtr->strides.cStride; + dstPtrRow = dstPtrChannel; + + for(int i = 0; i < roi.xywhROI.roiHeight; i++) + { + Rpp16f *srcPtr1TempR, *srcPtr1TempG, *srcPtr1TempB, *srcPtr2TempR, *srcPtr2TempG, *srcPtr2TempB, *dstPtrTemp; + srcPtr2TempR = srcPtr2RowR; + srcPtr2TempG = srcPtr2RowG; + srcPtr2TempB = srcPtr2RowB; + dstPtrTemp = dstPtrRow; + int vectorLoopCount = 0; + + if(i >= patchRoi->xywhROI.xy.y && i < (patchRoi->xywhROI.xy.y + cropRoi->xywhROI.roiHeight)) + { +#if __AVX2__ + for(; vectorLoopCount < patchAlignedLength1; vectorLoopCount+=4) + { + Rpp32f srcPtrTemp_ps[12], dstPtrTemp_ps[13]; + for(int cnt = 0; cnt < 4; cnt++) + { + *(srcPtrTemp_ps + cnt) = (Rpp32f) *(srcPtr2TempR + cnt); + *(srcPtrTemp_ps + 4 + cnt) = (Rpp32f) *(srcPtr2TempG + cnt); + *(srcPtrTemp_ps + 8 + cnt) = (Rpp32f) *(srcPtr2TempB + cnt); + } + __m128 p[4]; + rpp_simd_load(rpp_load12_f32pln3_to_f32pln3, srcPtrTemp_ps, srcPtrTemp_ps + 4, srcPtrTemp_ps + 4, p); // simd loads + rpp_simd_store(rpp_store12_f32pln3_to_f32pkd3, dstPtrTemp_ps, p); // simd stores + for(int cnt = 0; cnt < 12; cnt++) + *(dstPtrTemp + cnt) = (Rpp16f) *(dstPtrTemp_ps + cnt); + + srcPtr2TempR += vectorIncrementPerChannel; + srcPtr2TempG += vectorIncrementPerChannel; + srcPtr2TempB += vectorIncrementPerChannel; + dstPtrTemp += vectorIncrement; + } +#endif + for (; vectorLoopCount < patchBufferLength1; vectorLoopCount++) + { + dstPtrTemp[0] = *srcPtr2TempR++; + dstPtrTemp[1] = *srcPtr2TempG++; + dstPtrTemp[2] = *srcPtr2TempB++; + dstPtrTemp += 3; + } + vectorLoopCount = 0; + srcPtr1TempR = srcPtr1RowR; + srcPtr1TempG = srcPtr1RowG; + srcPtr1TempB = srcPtr1RowB; +#if __AVX2__ + for (; vectorLoopCount < cropAlignedLength; vectorLoopCount+=4) + { + Rpp32f srcPtrTemp_ps[12], dstPtrTemp_ps[13]; + for(int cnt = 0; cnt < 4; cnt++) + { + *(srcPtrTemp_ps + cnt) = (Rpp32f) *(srcPtr1TempR + cnt); + *(srcPtrTemp_ps + 4 + cnt) = (Rpp32f) *(srcPtr1TempG + cnt); + *(srcPtrTemp_ps + 8 + cnt) = (Rpp32f) *(srcPtr1TempB + cnt); + } + __m128 p[4]; + rpp_simd_load(rpp_load12_f32pln3_to_f32pln3, srcPtrTemp_ps, srcPtrTemp_ps + 4, srcPtrTemp_ps + 8, p); // simd loads + rpp_simd_store(rpp_store12_f32pln3_to_f32pkd3, dstPtrTemp_ps, p); // simd stores + for(int cnt = 0; cnt < 12; cnt++) + *(dstPtrTemp + cnt) = (Rpp16f) *(dstPtrTemp_ps + cnt); + + srcPtr1TempR += vectorIncrementPerChannel; + srcPtr1TempG += vectorIncrementPerChannel; + srcPtr1TempB += vectorIncrementPerChannel; + dstPtrTemp += vectorIncrement; + } +#endif + for (; vectorLoopCount < cropBufferLength; vectorLoopCount++) + { + dstPtrTemp[0] = *srcPtr1TempR++; + dstPtrTemp[1] = *srcPtr1TempG++; + dstPtrTemp[2] = *srcPtr1TempB++; + dstPtrTemp += 3; + } + + vectorLoopCount = 0; + srcPtr2TempR += cropBufferLength; + srcPtr2TempG += cropBufferLength; + srcPtr2TempB += cropBufferLength; +#if __AVX2__ + for(; vectorLoopCount < patchAlignedLength2; vectorLoopCount+=4) + { + Rpp32f srcPtrTemp_ps[12], dstPtrTemp_ps[13]; + for(int cnt = 0; cnt < 4; cnt++) + { + *(srcPtrTemp_ps + cnt) = (Rpp32f) *(srcPtr2TempR + cnt); + *(srcPtrTemp_ps + 4 + cnt) = (Rpp32f) *(srcPtr2TempG + cnt); + *(srcPtrTemp_ps + 8 + cnt) = (Rpp32f) *(srcPtr2TempB + cnt); + } + __m128 p[4]; + rpp_simd_load(rpp_load12_f32pln3_to_f32pln3, srcPtrTemp_ps, srcPtrTemp_ps + 4, srcPtrTemp_ps + 8, p); // simd loads + rpp_simd_store(rpp_store12_f32pln3_to_f32pkd3, dstPtrTemp_ps, p); // simd stores + for(int cnt = 0; cnt < 12; cnt++) + *(dstPtrTemp + cnt) = (Rpp16f) *(dstPtrTemp_ps + cnt); + + srcPtr2TempR += vectorIncrementPerChannel; + srcPtr2TempG += vectorIncrementPerChannel; + srcPtr2TempB += vectorIncrementPerChannel; + dstPtrTemp += vectorIncrement; + } +#endif + for (; vectorLoopCount < patchBufferLength2; vectorLoopCount++) + { + dstPtrTemp[0] = *srcPtr2TempR++; + dstPtrTemp[1] = *srcPtr2TempG++; + dstPtrTemp[2] = *srcPtr2TempB++; + dstPtrTemp += 3; + } + srcPtr1RowR += srcDescPtr->strides.hStride; + srcPtr1RowG += srcDescPtr->strides.hStride; + srcPtr1RowB += srcDescPtr->strides.hStride; + } + else + { +#if __AVX2__ + for(; vectorLoopCount < alignedLength; vectorLoopCount+=4) + { + Rpp32f srcPtrTemp_ps[12], dstPtrTemp_ps[13]; + for(int cnt = 0; cnt < 4; cnt++) + { + *(srcPtrTemp_ps + cnt) = (Rpp32f) *(srcPtr2TempR + cnt); + *(srcPtrTemp_ps + 4 + cnt) = (Rpp32f) *(srcPtr2TempG + cnt); + *(srcPtrTemp_ps + 8 + cnt) = (Rpp32f) *(srcPtr2TempB + cnt); + } + __m128 p[4]; + rpp_simd_load(rpp_load12_f32pln3_to_f32pln3, srcPtrTemp_ps, srcPtrTemp_ps + 4, srcPtrTemp_ps + 8, p); // simd loads + rpp_simd_store(rpp_store12_f32pln3_to_f32pkd3, dstPtrTemp_ps, p); // simd stores + for(int cnt = 0; cnt < 12; cnt++) + *(dstPtrTemp + cnt) = (Rpp16f) *(dstPtrTemp_ps + cnt); + + srcPtr2TempR += vectorIncrementPerChannel; + srcPtr2TempG += vectorIncrementPerChannel; + srcPtr2TempB += vectorIncrementPerChannel; + dstPtrTemp += vectorIncrement; + } +#endif + for (; vectorLoopCount < bufferLength; vectorLoopCount++) + { + dstPtrTemp[0] = *srcPtr2TempR++; + dstPtrTemp[1] = *srcPtr2TempG++; + dstPtrTemp[2] = *srcPtr2TempB++; + dstPtrTemp += 3; + } + } + + srcPtr2RowR += srcDescPtr->strides.hStride; + srcPtr2RowG += srcDescPtr->strides.hStride; + srcPtr2RowB += srcDescPtr->strides.hStride; + dstPtrRow += dstDescPtr->strides.hStride; + } + } + else if ((srcDescPtr->c == 3) && (srcDescPtr->layout == RpptLayout::NHWC) && (dstDescPtr->layout == RpptLayout::NHWC)) + { + Rpp32u bufferLengthInBytes = bufferLength * sizeof(Rpp16f); + Rpp32u cropBufferLengthInBytes = cropBufferLength * sizeof(Rpp16f); + Rpp32u patchBufferLength1InBytes = patchBufferLength1 * sizeof(Rpp16f); + Rpp32u patchBufferLength2InBytes = patchBufferLength2 * sizeof(Rpp16f); + + Rpp16f *srcPtr1Row, *srcPtr2Row, *dstPtrRow; + srcPtr1Row = srcPtr1Channel; + srcPtr2Row = srcPtr2Channel; + dstPtrRow = dstPtrChannel; + for(int i = 0; i < roi.xywhROI.roiHeight; i++) + { + Rpp16f *dstPtrRowTemp = dstPtrRow; + Rpp16f *srcPtr1RowTemp = srcPtr1Row; + Rpp16f *srcPtr2RowTemp = srcPtr2Row; + if(i >= patchRoi->xywhROI.xy.y && i < (patchRoi->xywhROI.xy.y + cropRoi->xywhROI.roiHeight)) + { + memcpy(dstPtrRowTemp, srcPtr2RowTemp, patchBufferLength1InBytes); + srcPtr2RowTemp += patchBufferLength1; + dstPtrRowTemp += patchBufferLength1; + memcpy(dstPtrRowTemp, srcPtr1RowTemp, cropBufferLengthInBytes); + srcPtr2RowTemp += cropBufferLength; + dstPtrRowTemp += cropBufferLength; + memcpy(dstPtrRowTemp, srcPtr2RowTemp, patchBufferLength2InBytes); + srcPtr1Row += srcDescPtr->strides.hStride; + } + else + { + memcpy(dstPtrRowTemp, srcPtr2RowTemp, bufferLengthInBytes); + } + + srcPtr2Row += srcDescPtr->strides.hStride; + dstPtrRow += dstDescPtr->strides.hStride; + } + } + else + { + Rpp32u bufferLengthInBytes = bufferLength * sizeof(Rpp16f); + Rpp32u cropBufferLengthInBytes = cropBufferLength * sizeof(Rpp16f); + Rpp32u patchBufferLength1InBytes = patchBufferLength1 * sizeof(Rpp16f); + Rpp32u patchBufferLength2InBytes = patchBufferLength2 * sizeof(Rpp16f); + for(int c = 0; c < layoutParams.channelParam; c++) + { + Rpp16f *srcPtr1Row, *srcPtr2Row, *dstPtrRow; + srcPtr1Row = srcPtr1Channel; + srcPtr2Row = srcPtr2Channel; + dstPtrRow = dstPtrChannel; + for(int i = 0; i < roi.xywhROI.roiHeight; i++) + { + Rpp16f *dstPtrRowTemp = dstPtrRow; + Rpp16f *srcPtr1RowTemp = srcPtr1Row; + Rpp16f *srcPtr2RowTemp = srcPtr2Row; + if(i >= patchRoi->xywhROI.xy.y && i < (patchRoi->xywhROI.xy.y + cropRoi->xywhROI.roiHeight)) + { + memcpy(dstPtrRowTemp, srcPtr2RowTemp, patchBufferLength1InBytes); + srcPtr2RowTemp += patchBufferLength1; + dstPtrRowTemp += patchBufferLength1; + memcpy(dstPtrRowTemp, srcPtr1RowTemp, cropBufferLengthInBytes); + srcPtr2RowTemp += cropBufferLength; + dstPtrRowTemp += cropBufferLength; + memcpy(dstPtrRowTemp, srcPtr2RowTemp, patchBufferLength2InBytes); + srcPtr1Row += srcDescPtr->strides.hStride; + } + else + { + memcpy(dstPtrRow, srcPtr2RowTemp, bufferLengthInBytes); + } + srcPtr2Row += srcDescPtr->strides.hStride; + dstPtrRow += dstDescPtr->strides.hStride; + } + srcPtr1Channel += srcDescPtr->strides.cStride; + srcPtr2Channel += srcDescPtr->strides.cStride; + dstPtrChannel += dstDescPtr->strides.cStride; + } + } + } + + return RPP_SUCCESS; +} + +RppStatus crop_and_patch_i8_i8_host_tensor(Rpp8s *srcPtr1, + Rpp8s *srcPtr2, + RpptDescPtr srcDescPtr, + Rpp8s *dstPtr, + RpptDescPtr dstDescPtr, + RpptROIPtr roiTensorPtrDst, + RpptROIPtr cropRoiTensor, + RpptROIPtr patchRoiTensor, + 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 = &roiTensorPtrDst[batchCount]; + compute_roi_validation_host(roiPtrInput, &roi, &roiDefault, roiType); + + RpptROIPtr cropRoi = &cropRoiTensor[batchCount]; + RpptROIPtr patchRoi = &patchRoiTensor[batchCount]; + Rpp8s *srcPtr1Image, *srcPtr2Image, *dstPtrImage; + srcPtr1Image = srcPtr1 + batchCount * srcDescPtr->strides.nStride; + srcPtr2Image = srcPtr2 + batchCount * srcDescPtr->strides.nStride; + dstPtrImage = dstPtr + batchCount * dstDescPtr->strides.nStride; + + Rpp32u bufferLength = roi.xywhROI.roiWidth * layoutParams.bufferMultiplier; + Rpp32u cropBufferLength = cropRoi->xywhROI.roiWidth * layoutParams.bufferMultiplier; + Rpp32u patchBufferLength1 = patchRoi->xywhROI.xy.x * layoutParams.bufferMultiplier; + Rpp32u patchBufferLength2 = (roi.xywhROI.roiWidth - (patchRoi->xywhROI.xy.x + cropRoi->xywhROI.roiWidth)) * layoutParams.bufferMultiplier; + Rpp32u vectorIncrement = 48; + Rpp32u vectorIncrementPerChannel = 16; + + Rpp8s *srcPtr1Channel, *srcPtr2Channel, *dstPtrChannel; + srcPtr1Channel = srcPtr1Image + (cropRoi->xywhROI.xy.y * srcDescPtr->strides.hStride) + (cropRoi->xywhROI.xy.x * layoutParams.bufferMultiplier); + srcPtr2Channel = srcPtr2Image + (roi.xywhROI.xy.y * srcDescPtr->strides.hStride) + (roi.xywhROI.xy.x * layoutParams.bufferMultiplier); + dstPtrChannel = dstPtrImage; + if ((srcDescPtr->c == 3) && (srcDescPtr->layout == RpptLayout::NHWC) && (dstDescPtr->layout == RpptLayout::NCHW)) + { + Rpp32u alignedLength = (bufferLength / 48) * 48; + Rpp32u cropAlignedLength = (cropBufferLength / 48) * 48; + Rpp32u patchAlignedLength1 = (patchBufferLength1 / 48) * 48; + Rpp32u patchAlignedLength2 = (patchBufferLength2 / 48) * 48; + + Rpp8s *srcPtr1Row, *srcPtr2Row, *dstPtrRowR, *dstPtrRowG, *dstPtrRowB; + srcPtr1Row = srcPtr1Channel; + srcPtr2Row = srcPtr2Channel; + dstPtrRowR = dstPtrChannel; + dstPtrRowG = dstPtrRowR + dstDescPtr->strides.cStride; + dstPtrRowB = dstPtrRowG + dstDescPtr->strides.cStride; + for(int i = 0; i < roi.xywhROI.roiHeight; i++) + { + Rpp8s *srcPtr1Temp, *srcPtr2Temp, *dstPtrTempR, *dstPtrTempG, *dstPtrTempB; + srcPtr2Temp = srcPtr2Row; + dstPtrTempR = dstPtrRowR; + dstPtrTempG = dstPtrRowG; + dstPtrTempB = dstPtrRowB; + int vectorLoopCount = 0; + if(i >= patchRoi->xywhROI.xy.y && i < (patchRoi->xywhROI.xy.y + cropRoi->xywhROI.roiHeight)) + { +#if __AVX2__ + for(; vectorLoopCount < patchAlignedLength1; vectorLoopCount += vectorIncrement) + { + __m128i px[3]; + rpp_simd_load(rpp_load48_i8pkd3_to_i8pln3, srcPtr2Temp, px); // simd loads + rpp_simd_store(rpp_store48_i8pln3_to_i8pln3, dstPtrTempR, dstPtrTempG, dstPtrTempB, px); // simd stores + srcPtr2Temp += vectorIncrement; + dstPtrTempR += vectorIncrementPerChannel; + dstPtrTempG += vectorIncrementPerChannel; + dstPtrTempB += vectorIncrementPerChannel; + } +#endif + for (; vectorLoopCount < patchBufferLength1; vectorLoopCount += 3) + { + *dstPtrTempR++ = srcPtr2Temp[0]; + *dstPtrTempG++ = srcPtr2Temp[1]; + *dstPtrTempB++ = srcPtr2Temp[2]; + srcPtr2Temp += 3; + + } + + vectorLoopCount = 0; + srcPtr1Temp = srcPtr1Row; +#if __AVX2__ + for (; vectorLoopCount < cropAlignedLength; vectorLoopCount += vectorIncrement) + { + __m128i px[3]; + rpp_simd_load(rpp_load48_i8pkd3_to_i8pln3, srcPtr1Temp, px); // simd loads + rpp_simd_store(rpp_store48_i8pln3_to_i8pln3, dstPtrTempR, dstPtrTempG, dstPtrTempB, px); // simd stores + srcPtr1Temp += vectorIncrement; + dstPtrTempR += vectorIncrementPerChannel; + dstPtrTempG += vectorIncrementPerChannel; + dstPtrTempB += vectorIncrementPerChannel; + } +#endif + for (; vectorLoopCount < cropBufferLength; vectorLoopCount += 3) + { + *dstPtrTempR++ = srcPtr1Temp[0]; + *dstPtrTempG++ = srcPtr1Temp[1]; + *dstPtrTempB++ = srcPtr1Temp[2]; + srcPtr1Temp += 3; + } + + vectorLoopCount = 0; + srcPtr2Temp += cropBufferLength; +#if __AVX2__ + for(; vectorLoopCount < patchAlignedLength2; vectorLoopCount += vectorIncrement) + { + __m128i px[3]; + rpp_simd_load(rpp_load48_i8pkd3_to_i8pln3, srcPtr2Temp, px); // simd loads + rpp_simd_store(rpp_store48_i8pln3_to_i8pln3, dstPtrTempR, dstPtrTempG, dstPtrTempB, px); // simd stores + srcPtr2Temp += vectorIncrement; + dstPtrTempR += vectorIncrementPerChannel; + dstPtrTempG += vectorIncrementPerChannel; + dstPtrTempB += vectorIncrementPerChannel; + } +#endif + for (; vectorLoopCount < patchBufferLength2; vectorLoopCount += 3) + { + *dstPtrTempR++ = srcPtr2Temp[0]; + *dstPtrTempG++ = srcPtr2Temp[1]; + *dstPtrTempB++ = srcPtr2Temp[2]; + srcPtr2Temp += 3; + } + srcPtr1Row += srcDescPtr->strides.hStride; + } + else + { +#if __AVX2__ + for(; vectorLoopCount < alignedLength; vectorLoopCount += vectorIncrement) + { + __m128i px[3]; + rpp_simd_load(rpp_load48_i8pkd3_to_i8pln3, srcPtr2Temp, px); // simd loads + rpp_simd_store(rpp_store48_i8pln3_to_i8pln3, dstPtrTempR, dstPtrTempG, dstPtrTempB, px); // simd stores + srcPtr2Temp += vectorIncrement; + dstPtrTempR += vectorIncrementPerChannel; + dstPtrTempG += vectorIncrementPerChannel; + dstPtrTempB += vectorIncrementPerChannel; + } +#endif + for (; vectorLoopCount < bufferLength; vectorLoopCount += 3) + { + *dstPtrTempR++ = srcPtr2Temp[0]; + *dstPtrTempG++ = srcPtr2Temp[1]; + *dstPtrTempB++ = srcPtr2Temp[2]; + srcPtr2Temp += 3; + } + } + + srcPtr2Row += srcDescPtr->strides.hStride; + dstPtrRowR += dstDescPtr->strides.hStride; + dstPtrRowG += dstDescPtr->strides.hStride; + dstPtrRowB += dstDescPtr->strides.hStride; + } + } + + else if ((srcDescPtr->c == 3) && (srcDescPtr->layout == RpptLayout::NCHW) && (dstDescPtr->layout == RpptLayout::NHWC)) + { + Rpp32u alignedLength = (bufferLength / 48) * 48; + Rpp32u cropAlignedLength = (cropBufferLength / 48) * 48; + Rpp32u patchAlignedLength1 = (patchBufferLength1 / 48) * 48; + Rpp32u patchAlignedLength2 = (patchBufferLength2 / 48) * 48; + + Rpp8s *srcPtr1RowR, *srcPtr1RowG, *srcPtr1RowB, *srcPtr2RowR, *srcPtr2RowG, *srcPtr2RowB, *dstPtrRow; + srcPtr1RowR = srcPtr1Channel; + srcPtr1RowG = srcPtr1RowR + srcDescPtr->strides.cStride; + srcPtr1RowB = srcPtr1RowG + srcDescPtr->strides.cStride; + srcPtr2RowR = srcPtr2Channel; + srcPtr2RowG = srcPtr2RowR + srcDescPtr->strides.cStride; + srcPtr2RowB = srcPtr2RowG + srcDescPtr->strides.cStride; + dstPtrRow = dstPtrChannel; + for(int i = 0; i < roi.xywhROI.roiHeight; i++) + { + Rpp8s *srcPtr1TempR, *srcPtr1TempG, *srcPtr1TempB, *srcPtr2TempR, *srcPtr2TempG, *srcPtr2TempB, *dstPtrTemp; + srcPtr2TempR = srcPtr2RowR; + srcPtr2TempG = srcPtr2RowG; + srcPtr2TempB = srcPtr2RowB; + dstPtrTemp = dstPtrRow; + int vectorLoopCount = 0; + if(i >= patchRoi->xywhROI.xy.y && i < (patchRoi->xywhROI.xy.y + cropRoi->xywhROI.roiHeight)) + { +#if __AVX2__ + for(; vectorLoopCount < patchAlignedLength1; vectorLoopCount += vectorIncrementPerChannel) + { + __m128i px[3]; + rpp_simd_load(rpp_load48_i8pln3_to_i8pln3, srcPtr2TempR, srcPtr2TempG, srcPtr2TempB, px); // simd loads + rpp_simd_store(rpp_store48_i8pln3_to_i8pkd3, dstPtrTemp, px); // simd stores + srcPtr2TempR += vectorIncrementPerChannel; + srcPtr2TempG += vectorIncrementPerChannel; + srcPtr2TempB += vectorIncrementPerChannel; + dstPtrTemp += vectorIncrement; + } +#endif + for (; vectorLoopCount < patchBufferLength1; vectorLoopCount++) + { + dstPtrTemp[0] = *srcPtr2TempR++; + dstPtrTemp[1] = *srcPtr2TempG++; + dstPtrTemp[2] = *srcPtr2TempB++; + dstPtrTemp += 3; + } + + vectorLoopCount = 0; + srcPtr1TempR = srcPtr1RowR; + srcPtr1TempG = srcPtr1RowG; + srcPtr1TempB = srcPtr1RowB; +#if __AVX2__ + for (; vectorLoopCount < cropAlignedLength; vectorLoopCount += vectorIncrementPerChannel) + { + __m128i px[3]; + rpp_simd_load(rpp_load48_i8pln3_to_i8pln3, srcPtr1TempR, srcPtr1TempG, srcPtr1TempB, px); // simd loads + rpp_simd_store(rpp_store48_i8pln3_to_i8pkd3, dstPtrTemp, px); // simd stores + srcPtr1TempR += vectorIncrementPerChannel; + srcPtr1TempG += vectorIncrementPerChannel; + srcPtr1TempB += vectorIncrementPerChannel; + dstPtrTemp += vectorIncrement; + } +#endif + for (; vectorLoopCount < cropBufferLength; vectorLoopCount++) + { + dstPtrTemp[0] = *srcPtr1TempR++; + dstPtrTemp[1] = *srcPtr1TempG++; + dstPtrTemp[2] = *srcPtr1TempB++; + dstPtrTemp += 3; + } + + vectorLoopCount = 0; + srcPtr2TempR += cropBufferLength; + srcPtr2TempG += cropBufferLength; + srcPtr2TempB += cropBufferLength; +#if __AVX2__ + for(; vectorLoopCount < patchAlignedLength2; vectorLoopCount += vectorIncrementPerChannel) + { + __m128i px[3]; + rpp_simd_load(rpp_load48_i8pln3_to_i8pln3, srcPtr2TempR, srcPtr2TempG, srcPtr2TempB, px); // simd loads + rpp_simd_store(rpp_store48_i8pln3_to_i8pkd3, dstPtrTemp, px); // simd stores + srcPtr2TempR += vectorIncrementPerChannel; + srcPtr2TempG += vectorIncrementPerChannel; + srcPtr2TempB += vectorIncrementPerChannel; + dstPtrTemp += vectorIncrement; + } +#endif + for (; vectorLoopCount < patchBufferLength2; vectorLoopCount++) + { + dstPtrTemp[0] = *srcPtr2TempR++; + dstPtrTemp[1] = *srcPtr2TempG++; + dstPtrTemp[2] = *srcPtr2TempB++; + dstPtrTemp += 3; + } + srcPtr1RowR += srcDescPtr->strides.hStride; + srcPtr1RowG += srcDescPtr->strides.hStride; + srcPtr1RowB += srcDescPtr->strides.hStride; + } + else + { +#if __AVX2__ + for(; vectorLoopCount < alignedLength; vectorLoopCount += vectorIncrementPerChannel) + { + __m128i px[3]; + rpp_simd_load(rpp_load48_i8pln3_to_i8pln3, srcPtr2TempR, srcPtr2TempG, srcPtr2TempB, px); // simd loads + rpp_simd_store(rpp_store48_i8pln3_to_i8pkd3, dstPtrTemp, px); // simd stores + srcPtr2TempR += vectorIncrementPerChannel; + srcPtr2TempG += vectorIncrementPerChannel; + srcPtr2TempB += vectorIncrementPerChannel; + dstPtrTemp += vectorIncrement; + } +#endif + for (; vectorLoopCount < bufferLength; vectorLoopCount++) + { + dstPtrTemp[0] = *srcPtr2TempR++; + dstPtrTemp[1] = *srcPtr2TempG++; + dstPtrTemp[2] = *srcPtr2TempB++; + dstPtrTemp += 3; + } + } + srcPtr2RowR += srcDescPtr->strides.hStride; + srcPtr2RowG += srcDescPtr->strides.hStride; + srcPtr2RowB += srcDescPtr->strides.hStride; + dstPtrRow += dstDescPtr->strides.hStride; + } + } + else if ((srcDescPtr->c == 3) && (srcDescPtr->layout == RpptLayout::NHWC) && (dstDescPtr->layout == RpptLayout::NHWC)) + { + Rpp8s *srcPtr1Row, *srcPtr2Row, *dstPtrRow; + srcPtr1Row = srcPtr1Channel; + srcPtr2Row = srcPtr2Channel; + dstPtrRow = dstPtrChannel; + for(int i = 0; i < roi.xywhROI.roiHeight; i++) + { + Rpp8s *dstPtrRowTemp = dstPtrRow; + Rpp8s *srcPtr1RowTemp = srcPtr1Row; + Rpp8s *srcPtr2RowTemp = srcPtr2Row; + if(i >= patchRoi->xywhROI.xy.y && i < (patchRoi->xywhROI.xy.y + cropRoi->xywhROI.roiHeight)) + { + memcpy(dstPtrRowTemp, srcPtr2RowTemp, patchBufferLength1); + srcPtr2RowTemp += patchBufferLength1; + dstPtrRowTemp += patchBufferLength1; + memcpy(dstPtrRowTemp, srcPtr1RowTemp, cropBufferLength); + srcPtr2RowTemp += cropBufferLength; + dstPtrRowTemp += cropBufferLength; + memcpy(dstPtrRowTemp, srcPtr2RowTemp, patchBufferLength2); + srcPtr1Row += srcDescPtr->strides.hStride; + } + else + { + memcpy(dstPtrRowTemp, srcPtr2RowTemp, bufferLength); + } + srcPtr2Row += srcDescPtr->strides.hStride; + dstPtrRow += dstDescPtr->strides.hStride; + } + } + else + { + for(int c = 0; c < layoutParams.channelParam; c++) + { + Rpp8s *srcPtr1Row, *srcPtr2Row, *dstPtrRow; + srcPtr1Row = srcPtr1Channel; + srcPtr2Row = srcPtr2Channel; + dstPtrRow = dstPtrChannel; + for(int i = 0; i < roi.xywhROI.roiHeight; i++) + { + Rpp8s *dstPtrRowTemp = dstPtrRow; + Rpp8s *srcPtr1RowTemp = srcPtr1Row; + Rpp8s *srcPtr2RowTemp = srcPtr2Row; + if(i >= patchRoi->xywhROI.xy.y && i < (patchRoi->xywhROI.xy.y + cropRoi->xywhROI.roiHeight)) + { + memcpy(dstPtrRowTemp, srcPtr2RowTemp, patchBufferLength1); + srcPtr2RowTemp += patchBufferLength1; + dstPtrRowTemp += patchBufferLength1; + memcpy(dstPtrRowTemp, srcPtr1RowTemp, cropBufferLength); + srcPtr2RowTemp += cropBufferLength; + dstPtrRowTemp += cropBufferLength; + memcpy(dstPtrRowTemp, srcPtr2RowTemp, patchBufferLength2); + srcPtr1Row += srcDescPtr->strides.hStride; + } + else + { + memcpy(dstPtrRow, srcPtr2RowTemp, bufferLength); + } + srcPtr2Row += srcDescPtr->strides.hStride; + dstPtrRow += dstDescPtr->strides.hStride; + } + srcPtr1Channel += srcDescPtr->strides.cStride; + srcPtr2Channel += srcDescPtr->strides.cStride; + dstPtrChannel += dstDescPtr->strides.cStride; + } + } + } + + return RPP_SUCCESS; +} + diff --git a/src/modules/hip/hip_tensor_geometric_augmentations.hpp b/src/modules/hip/hip_tensor_geometric_augmentations.hpp index 3acdf4cfb..e64f3b21d 100644 --- a/src/modules/hip/hip_tensor_geometric_augmentations.hpp +++ b/src/modules/hip/hip_tensor_geometric_augmentations.hpp @@ -34,6 +34,7 @@ SOFTWARE. #include "kernel/resize_crop_mirror.hpp" #include "kernel/phase.hpp" #include "kernel/slice.hpp" +#include "kernel/crop_and_patch.hpp" #include "kernel/flip_voxel.hpp" #endif // HIP_TENSOR_GEOMETRIC_AUGMENTATIONS_HPP diff --git a/src/modules/hip/kernel/crop_and_patch.hpp b/src/modules/hip/kernel/crop_and_patch.hpp new file mode 100644 index 000000000..4236b1607 --- /dev/null +++ b/src/modules/hip/kernel/crop_and_patch.hpp @@ -0,0 +1,426 @@ +#include +#include "rpp_hip_common.hpp" + +// -------------------- Set 0 - crop and patch device helpers -------------------- + +__device__ void update_data(int startLoc, d_float24 *dst_f24, d_float24 *temp_f24) +{ + for(uint i = startLoc, j = 0; i < 8; i++, j++) + { + dst_f24->f8[0].f1[i] = temp_f24->f8[0].f1[j]; + dst_f24->f8[1].f1[i] = temp_f24->f8[1].f1[j]; + dst_f24->f8[2].f1[i] = temp_f24->f8[2].f1[j]; + } +} + +__device__ void update_data(int startLoc, d_float8 *dst_f8, d_float8 *temp_f8) +{ + for(uint i = startLoc, j = 0; i < 8; i++, j++) + dst_f8->f1[i] = temp_f8->f1[j]; +} + +// -------------------- Set 1 - crop and patch main kernels -------------------- +template +__global__ void crop_and_patch_pkd_hip_tensor(T *srcPtr1, + T *srcPtr2, + uint2 srcStridesNH, + T *dstPtr, + uint2 dstStridesNH, + RpptROIPtr roiTensorPtrSrc, + RpptROIPtr cropTensorPtrSrc, + RpptROIPtr patchTensorPtrSrc) +{ + 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; + } + + // check if the co-ordinates is within the patch region + d_float24 dst_f24; + bool rowCheck = (id_y >= patchTensorPtrSrc[id_z].xywhROI.xy.y) && (id_y < (patchTensorPtrSrc[id_z].xywhROI.xy.y + cropTensorPtrSrc[id_z].xywhROI.roiHeight)); + bool colCheck = (id_x >= patchTensorPtrSrc[id_z].xywhROI.xy.x) && (id_x < (patchTensorPtrSrc[id_z].xywhROI.xy.x + cropTensorPtrSrc[id_z].xywhROI.roiWidth)); + uint patchStart = patchTensorPtrSrc[id_z].xywhROI.xy.x; + uint dstIdx = (id_z * dstStridesNH.x) + (id_y * dstStridesNH.y) + id_x * 3; + if(rowCheck && colCheck) + { + uint srcIdx1 = (id_z * srcStridesNH.x) + (id_y - patchTensorPtrSrc[id_z].xywhROI.xy.y + cropTensorPtrSrc[id_z].xywhROI.xy.y) * srcStridesNH.y + (id_x - patchTensorPtrSrc[id_z].xywhROI.xy.x + cropTensorPtrSrc[id_z].xywhROI.xy.x) * 3; + uint patchEnd = patchTensorPtrSrc[id_z].xywhROI.xy.x + cropTensorPtrSrc[id_z].xywhROI.roiWidth; + rpp_hip_load24_pkd3_and_unpack_to_float24_pln3(srcPtr1 + srcIdx1, &dst_f24); + // to handle the case when loaded data goes beyond the bounds of patch region + if((id_x + 8) >= patchEnd) + { + uint srcIdx2 = (id_z * srcStridesNH.x) + ((id_y + roiTensorPtrSrc[id_z].xywhROI.xy.y) * srcStridesNH.y) + (patchEnd + roiTensorPtrSrc[id_z].xywhROI.xy.x) * 3; + d_float24 temp_f24; + rpp_hip_load24_pkd3_and_unpack_to_float24_pln3(srcPtr2 + srcIdx2, &temp_f24); + update_data(patchEnd - id_x, &dst_f24, &temp_f24); + } + rpp_hip_pack_float24_pln3_and_store24_pkd3(dstPtr + dstIdx, &dst_f24); + } + // to handle the case when loaded data goes beyond the input region and enters the patch region + else if(rowCheck && (id_x + 8) >= patchStart) + { + uint srcIdx2 = (id_z * srcStridesNH.x) + ((id_y + roiTensorPtrSrc[id_z].xywhROI.xy.y) * srcStridesNH.y) + (id_x + roiTensorPtrSrc[id_z].xywhROI.xy.x) * 3; + rpp_hip_load24_pkd3_and_unpack_to_float24_pln3(srcPtr2 + srcIdx2, &dst_f24); + uint srcIdx1 = (id_z * srcStridesNH.x) + ((id_y - patchTensorPtrSrc[id_z].xywhROI.xy.y + cropTensorPtrSrc[id_z].xywhROI.xy.y) * srcStridesNH.y) + cropTensorPtrSrc[id_z].xywhROI.xy.x * 3; + d_float24 temp_f24; + rpp_hip_load24_pkd3_and_unpack_to_float24_pln3(srcPtr1 + srcIdx1, &temp_f24); + update_data(patchStart - id_x, &dst_f24, &temp_f24); + rpp_hip_pack_float24_pln3_and_store24_pkd3(dstPtr + dstIdx, &dst_f24); + } +} + +template +__global__ void crop_and_patch_pln3_hip_tensor(T *srcPtr1, + T *srcPtr2, + uint3 srcStridesNCH, + T *dstPtr, + uint3 dstStridesNCH, + RpptROIPtr roiTensorPtrSrc, + RpptROIPtr cropTensorPtrSrc, + RpptROIPtr patchTensorPtrSrc) +{ + 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; + } + + d_float24 dst_f24; + // check if the co-ordinates is within the patch region + bool rowCheck = (id_y >= patchTensorPtrSrc[id_z].xywhROI.xy.y) && (id_y < (patchTensorPtrSrc[id_z].xywhROI.xy.y + cropTensorPtrSrc[id_z].xywhROI.roiHeight)); + bool colCheck = (id_x >= patchTensorPtrSrc[id_z].xywhROI.xy.x) && (id_x < (patchTensorPtrSrc[id_z].xywhROI.xy.x + cropTensorPtrSrc[id_z].xywhROI.roiWidth)); + uint patchStart = patchTensorPtrSrc[id_z].xywhROI.xy.x; + uint dstIdx = (id_z * dstStridesNCH.x) + (id_y * dstStridesNCH.z) + id_x; + if(rowCheck && colCheck) + { + uint srcIdx1 = (id_z * srcStridesNCH.x) + (id_y - patchTensorPtrSrc[id_z].xywhROI.xy.y + cropTensorPtrSrc[id_z].xywhROI.xy.y) * srcStridesNCH.z + (id_x - patchTensorPtrSrc[id_z].xywhROI.xy.x + cropTensorPtrSrc[id_z].xywhROI.xy.x); + uint patchEnd = patchTensorPtrSrc[id_z].xywhROI.xy.x + cropTensorPtrSrc[id_z].xywhROI.roiWidth; + rpp_hip_load24_pln3_and_unpack_to_float24_pln3(srcPtr1 + srcIdx1, srcStridesNCH.y, &dst_f24); + // to handle the case when loaded data goes beyond the bounds of patch region + if((id_x + 8) >= patchEnd) + { + uint srcIdx2 = (id_z * srcStridesNCH.x) + ((id_y + roiTensorPtrSrc[id_z].xywhROI.xy.y) * srcStridesNCH.z) + (patchEnd + roiTensorPtrSrc[id_z].xywhROI.xy.x); + d_float24 temp_f24; + rpp_hip_load24_pln3_and_unpack_to_float24_pln3(srcPtr2 + srcIdx2, srcStridesNCH.y, &temp_f24); + update_data(patchEnd - id_x, &dst_f24, &temp_f24); + } + rpp_hip_pack_float24_pln3_and_store24_pln3(dstPtr + dstIdx, dstStridesNCH.y, &dst_f24); + } + // to handle the case when loaded data goes beyond the input region and enters the patch region + else if(rowCheck && (id_x + 8) >= patchStart) + { + uint srcIdx2 = (id_z * srcStridesNCH.x) + ((id_y + roiTensorPtrSrc[id_z].xywhROI.xy.y) * srcStridesNCH.z) + (id_x + roiTensorPtrSrc[id_z].xywhROI.xy.x); + rpp_hip_load24_pln3_and_unpack_to_float24_pln3(srcPtr2 + srcIdx2, srcStridesNCH.y, &dst_f24); + uint srcIdx1 = (id_z * srcStridesNCH.x) + ((id_y - patchTensorPtrSrc[id_z].xywhROI.xy.y + cropTensorPtrSrc[id_z].xywhROI.xy.y) * srcStridesNCH.z) + cropTensorPtrSrc[id_z].xywhROI.xy.x; + d_float24 temp_f24; + rpp_hip_load24_pln3_and_unpack_to_float24_pln3(srcPtr1 + srcIdx1, srcStridesNCH.y, &temp_f24); + update_data(patchStart - id_x, &dst_f24, &temp_f24); + rpp_hip_pack_float24_pln3_and_store24_pln3(dstPtr + dstIdx, dstStridesNCH.y, &dst_f24); + } +} + +template +__global__ void crop_and_patch_pln1_hip_tensor(T *srcPtr1, + T *srcPtr2, + uint2 srcStridesNH, + T *dstPtr, + uint2 dstStridesNH, + RpptROIPtr roiTensorPtrSrc, + RpptROIPtr cropTensorPtrSrc, + RpptROIPtr patchTensorPtrSrc) +{ + 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; + } + + // check if the co-ordinates is within the patch region + bool rowCheck = (id_y >= patchTensorPtrSrc[id_z].xywhROI.xy.y) && (id_y < (patchTensorPtrSrc[id_z].xywhROI.xy.y + cropTensorPtrSrc[id_z].xywhROI.roiHeight)); + bool colCheck = (id_x >= patchTensorPtrSrc[id_z].xywhROI.xy.x) && (id_x < (patchTensorPtrSrc[id_z].xywhROI.xy.x + cropTensorPtrSrc[id_z].xywhROI.roiWidth)); + uint dstIdx = (id_z * dstStridesNH.x) + (id_y * dstStridesNH.y) + id_x; + uint patchStart = patchTensorPtrSrc[id_z].xywhROI.xy.x; + if(rowCheck && colCheck) + { + uint srcIdx1 = (id_z * srcStridesNH.x) + (id_y - patchTensorPtrSrc[id_z].xywhROI.xy.y + cropTensorPtrSrc[id_z].xywhROI.xy.y) * srcStridesNH.y + (id_x - patchTensorPtrSrc[id_z].xywhROI.xy.x + cropTensorPtrSrc[id_z].xywhROI.xy.x); + uint dstIdx = (id_z * dstStridesNH.x) + (id_y * dstStridesNH.y) + id_x; + uint patchEnd = patchTensorPtrSrc[id_z].xywhROI.xy.x + cropTensorPtrSrc[id_z].xywhROI.roiWidth; + + d_float8 dst_f8; + rpp_hip_load8_and_unpack_to_float8(srcPtr1 + srcIdx1, &dst_f8); + uint srcIdx2; + // to handle the case when loaded data goes beyond the bounds of patch region + if((id_x + 8) >= patchEnd) + { + d_float8 temp_f8; + srcIdx2 = (id_z * srcStridesNH.x) + ((id_y + roiTensorPtrSrc[id_z].xywhROI.xy.y) * srcStridesNH.y) + (patchEnd + roiTensorPtrSrc[id_z].xywhROI.xy.x); + rpp_hip_load8_and_unpack_to_float8(srcPtr2 + srcIdx2, &temp_f8); + update_data(patchEnd - id_x, &dst_f8, &temp_f8); + } + rpp_hip_pack_float8_and_store8(dstPtr + dstIdx, &dst_f8); + } + // to handle the case when loaded data goes beyond the input region and enters the patch region + else if(rowCheck && (id_x + 8) >= patchStart) + { + d_float8 dst_f8, temp_f8; + uint srcIdx2 = (id_z * srcStridesNH.x) + ((id_y + roiTensorPtrSrc[id_z].xywhROI.xy.y) * srcStridesNH.y) + (id_x + roiTensorPtrSrc[id_z].xywhROI.xy.x); + rpp_hip_load8_and_unpack_to_float8(srcPtr2 + srcIdx2, &dst_f8); + + uint srcIdx1 = (id_z * srcStridesNH.x) + ((id_y - patchTensorPtrSrc[id_z].xywhROI.xy.y + cropTensorPtrSrc[id_z].xywhROI.xy.y) * srcStridesNH.y) + cropTensorPtrSrc[id_z].xywhROI.xy.x; + rpp_hip_load8_and_unpack_to_float8(srcPtr1 + srcIdx1, &temp_f8); + update_data(patchStart - id_x, &dst_f8, &temp_f8); + rpp_hip_pack_float8_and_store8(dstPtr + dstIdx, &dst_f8); + } +} + +template +__global__ void crop_and_patch_pkd3_pln3_hip_tensor(T *srcPtr1, + T *srcPtr2, + uint2 srcStridesNH, + T *dstPtr, + uint3 dstStridesNCH, + RpptROIPtr roiTensorPtrSrc, + RpptROIPtr cropTensorPtrSrc, + RpptROIPtr patchTensorPtrSrc) +{ + 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; + } + + // check if the co-ordinates is within the patch region + d_float24 dst_f24; + bool rowCheck = (id_y >= patchTensorPtrSrc[id_z].xywhROI.xy.y) && (id_y < (patchTensorPtrSrc[id_z].xywhROI.xy.y + cropTensorPtrSrc[id_z].xywhROI.roiHeight)); + bool colCheck = (id_x >= patchTensorPtrSrc[id_z].xywhROI.xy.x) && (id_x < (patchTensorPtrSrc[id_z].xywhROI.xy.x + cropTensorPtrSrc[id_z].xywhROI.roiWidth)); + uint patchStart = patchTensorPtrSrc[id_z].xywhROI.xy.x; + uint dstIdx = (id_z * dstStridesNCH.x) + (id_y * dstStridesNCH.z) + id_x; + if(rowCheck && colCheck) + { + uint srcIdx1 = (id_z * srcStridesNH.x) + (id_y - patchTensorPtrSrc[id_z].xywhROI.xy.y + cropTensorPtrSrc[id_z].xywhROI.xy.y) * srcStridesNH.y + (id_x - patchTensorPtrSrc[id_z].xywhROI.xy.x + cropTensorPtrSrc[id_z].xywhROI.xy.x) * 3; + uint patchEnd = patchTensorPtrSrc[id_z].xywhROI.xy.x + cropTensorPtrSrc[id_z].xywhROI.roiWidth; + rpp_hip_load24_pkd3_and_unpack_to_float24_pln3(srcPtr1 + srcIdx1, &dst_f24); + // to handle the case when loaded data goes beyond the bounds of patch region + if((id_x + 8) >= patchEnd) + { + uint srcIdx2 = (id_z * srcStridesNH.x) + ((id_y + roiTensorPtrSrc[id_z].xywhROI.xy.y) * srcStridesNH.y) + (patchEnd + roiTensorPtrSrc[id_z].xywhROI.xy.x) * 3; + d_float24 temp_f24; + rpp_hip_load24_pkd3_and_unpack_to_float24_pln3(srcPtr2 + srcIdx2, &temp_f24); + update_data(patchEnd - id_x, &dst_f24, &temp_f24); + } + rpp_hip_pack_float24_pln3_and_store24_pln3(dstPtr + dstIdx, dstStridesNCH.y, &dst_f24); + } + // to handle the case when loaded data goes beyond the input region and enters the patch region + else if(rowCheck && (id_x + 8) >= patchStart) + { + uint srcIdx2 = (id_z * srcStridesNH.x) + ((id_y + roiTensorPtrSrc[id_z].xywhROI.xy.y) * srcStridesNH.y) + (id_x + roiTensorPtrSrc[id_z].xywhROI.xy.x) * 3; + rpp_hip_load24_pkd3_and_unpack_to_float24_pln3(srcPtr2 + srcIdx2, &dst_f24); + uint srcIdx1 = (id_z * srcStridesNH.x) + ((id_y - patchTensorPtrSrc[id_z].xywhROI.xy.y + cropTensorPtrSrc[id_z].xywhROI.xy.y) * srcStridesNH.y) + cropTensorPtrSrc[id_z].xywhROI.xy.x * 3; + d_float24 temp_f24; + rpp_hip_load24_pkd3_and_unpack_to_float24_pln3(srcPtr1 + srcIdx1, &temp_f24); + update_data(patchStart - id_x, &dst_f24, &temp_f24); + rpp_hip_pack_float24_pln3_and_store24_pln3(dstPtr + dstIdx, dstStridesNCH.y, &dst_f24); + } +} + +template +__global__ void crop_and_patch_pln3_pkd3_hip_tensor(T *srcPtr1, + T *srcPtr2, + uint3 srcStridesNCH, + T *dstPtr, + uint2 dstStridesNH, + RpptROIPtr roiTensorPtrSrc, + RpptROIPtr cropTensorPtrSrc, + RpptROIPtr patchTensorPtrSrc) +{ + 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; + } + + d_float24 dst_f24; + // check if the co-ordinates is within the patch region + bool rowCheck = (id_y >= patchTensorPtrSrc[id_z].xywhROI.xy.y) && (id_y < (patchTensorPtrSrc[id_z].xywhROI.xy.y + cropTensorPtrSrc[id_z].xywhROI.roiHeight)); + bool colCheck = (id_x >= patchTensorPtrSrc[id_z].xywhROI.xy.x) && (id_x < (patchTensorPtrSrc[id_z].xywhROI.xy.x + cropTensorPtrSrc[id_z].xywhROI.roiWidth)); + uint patchStart = patchTensorPtrSrc[id_z].xywhROI.xy.x; + uint dstIdx = (id_z * dstStridesNH.x) + (id_y * dstStridesNH.y) + id_x * 3; + if(rowCheck && colCheck) + { + uint srcIdx1 = (id_z * srcStridesNCH.x) + (id_y - patchTensorPtrSrc[id_z].xywhROI.xy.y + cropTensorPtrSrc[id_z].xywhROI.xy.y) * srcStridesNCH.z + (id_x - patchTensorPtrSrc[id_z].xywhROI.xy.x + cropTensorPtrSrc[id_z].xywhROI.xy.x); + uint patchEnd = patchTensorPtrSrc[id_z].xywhROI.xy.x + cropTensorPtrSrc[id_z].xywhROI.roiWidth; + rpp_hip_load24_pln3_and_unpack_to_float24_pln3(srcPtr1 + srcIdx1, srcStridesNCH.y, &dst_f24); + // to handle the case when loaded data goes beyond the bounds of patch region + if((id_x + 8) >= patchEnd) + { + uint srcIdx2 = (id_z * srcStridesNCH.x) + ((id_y + roiTensorPtrSrc[id_z].xywhROI.xy.y) * srcStridesNCH.z) + (patchEnd + roiTensorPtrSrc[id_z].xywhROI.xy.x); + d_float24 temp_f24; + rpp_hip_load24_pln3_and_unpack_to_float24_pln3(srcPtr2 + srcIdx2, srcStridesNCH.y, &temp_f24); + update_data(patchEnd - id_x, &dst_f24, &temp_f24); + } + rpp_hip_pack_float24_pln3_and_store24_pkd3(dstPtr + dstIdx, &dst_f24); + } + // to handle the case when loaded data goes beyond the input region and enters the patch region + else if(rowCheck && (id_x + 8) >= patchStart) + { + uint srcIdx2 = (id_z * srcStridesNCH.x) + ((id_y + roiTensorPtrSrc[id_z].xywhROI.xy.y) * srcStridesNCH.z) + (id_x + roiTensorPtrSrc[id_z].xywhROI.xy.x); + rpp_hip_load24_pln3_and_unpack_to_float24_pln3(srcPtr2 + srcIdx2, srcStridesNCH.y, &dst_f24); + uint srcIdx1 = (id_z * srcStridesNCH.x) + ((id_y - patchTensorPtrSrc[id_z].xywhROI.xy.y + cropTensorPtrSrc[id_z].xywhROI.xy.y) * srcStridesNCH.z) + cropTensorPtrSrc[id_z].xywhROI.xy.x; + d_float24 temp_f24; + rpp_hip_load24_pln3_and_unpack_to_float24_pln3(srcPtr1 + srcIdx1, srcStridesNCH.y, &temp_f24); + update_data(patchStart - id_x, &dst_f24, &temp_f24); + rpp_hip_pack_float24_pln3_and_store24_pkd3(dstPtr + dstIdx, &dst_f24); + } +} + +// -------------------- Set 2 - Kernel Executors -------------------- +template +RppStatus hip_exec_crop_and_patch_tensor(T *srcPtr1, + T *srcPtr2, + RpptDescPtr srcDescPtr, + T *dstPtr, + RpptDescPtr dstDescPtr, + RpptROIPtr roiTensorPtrSrc, + RpptROIPtr cropTensorPtr, + RpptROIPtr patchTensorPtr, + RpptRoiType roiType, + rpp::Handle& handle) +{ + if (roiType == RpptRoiType::LTRB) + hip_exec_roi_converison_ltrb_to_xywh(roiTensorPtrSrc, handle); + + int globalThreads_x = (dstDescPtr->w + 7) >> 3; + int globalThreads_y = dstDescPtr->h; + int globalThreads_z = handle.GetBatchSize(); + + if ((srcDescPtr->layout == RpptLayout::NHWC) && (dstDescPtr->layout == RpptLayout::NHWC)) + { + hipMemcpyAsync(dstPtr, srcPtr2, static_cast(srcDescPtr->n * srcDescPtr->strides.nStride * sizeof(T)), hipMemcpyDeviceToDevice, handle.GetStream()); + hipStreamSynchronize(handle.GetStream()); + hipLaunchKernelGGL(crop_and_patch_pkd_hip_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(), + srcPtr1, + srcPtr2, + make_uint2(srcDescPtr->strides.nStride, srcDescPtr->strides.hStride), + dstPtr, + make_uint2(dstDescPtr->strides.nStride, dstDescPtr->strides.hStride), + roiTensorPtrSrc, + cropTensorPtr, + patchTensorPtr); + } + else if ((srcDescPtr->layout == RpptLayout::NCHW) && (dstDescPtr->layout == RpptLayout::NCHW)) + { + hipMemcpyAsync(dstPtr, srcPtr2, static_cast(srcDescPtr->n * srcDescPtr->strides.nStride * sizeof(T)), hipMemcpyDeviceToDevice, handle.GetStream()); + hipStreamSynchronize(handle.GetStream()); + if ((srcDescPtr->c == 3) && (dstDescPtr->c == 3)) + { + hipLaunchKernelGGL(crop_and_patch_pln3_hip_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(), + srcPtr1, + srcPtr2, + make_uint3(srcDescPtr->strides.nStride, srcDescPtr->strides.cStride, srcDescPtr->strides.hStride), + dstPtr, + make_uint3(dstDescPtr->strides.nStride, dstDescPtr->strides.cStride, dstDescPtr->strides.hStride), + roiTensorPtrSrc, + cropTensorPtr, + patchTensorPtr); + } + else + { + hipLaunchKernelGGL(crop_and_patch_pln1_hip_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(), + srcPtr1, + srcPtr2, + make_uint2(srcDescPtr->strides.nStride, srcDescPtr->strides.hStride), + dstPtr, + make_uint2(dstDescPtr->strides.nStride, dstDescPtr->strides.hStride), + roiTensorPtrSrc, + cropTensorPtr, + patchTensorPtr); + } + } + else if ((srcDescPtr->c == 3) && (dstDescPtr->c == 3)) + { + if ((srcDescPtr->layout == RpptLayout::NHWC) && (dstDescPtr->layout == RpptLayout::NCHW)) + { + hipLaunchKernelGGL(convert_pkd3_pln3_hip_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(), + srcPtr2, + make_uint2(srcDescPtr->strides.nStride, srcDescPtr->strides.hStride), + dstPtr, + make_uint3(dstDescPtr->strides.nStride, dstDescPtr->strides.cStride, dstDescPtr->strides.hStride), + roiTensorPtrSrc); + hipStreamSynchronize(handle.GetStream()); + hipLaunchKernelGGL(crop_and_patch_pkd3_pln3_hip_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(), + srcPtr1, + srcPtr2, + make_uint2(srcDescPtr->strides.nStride, srcDescPtr->strides.hStride), + dstPtr, + make_uint3(dstDescPtr->strides.nStride, dstDescPtr->strides.cStride, dstDescPtr->strides.hStride), + roiTensorPtrSrc, + cropTensorPtr, + patchTensorPtr); + } + else if ((srcDescPtr->layout == RpptLayout::NCHW) && (dstDescPtr->layout == RpptLayout::NHWC)) + { + hipLaunchKernelGGL(convert_pln3_pkd3_hip_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(), + srcPtr2, + make_uint3(srcDescPtr->strides.nStride, srcDescPtr->strides.cStride, srcDescPtr->strides.hStride), + dstPtr, + make_uint2(dstDescPtr->strides.nStride, dstDescPtr->strides.hStride), + roiTensorPtrSrc); + hipStreamSynchronize(handle.GetStream()); + hipLaunchKernelGGL(crop_and_patch_pln3_pkd3_hip_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(), + srcPtr1, + srcPtr2, + make_uint3(srcDescPtr->strides.nStride, srcDescPtr->strides.cStride, srcDescPtr->strides.hStride), + dstPtr, + make_uint2(dstDescPtr->strides.nStride, dstDescPtr->strides.hStride), + roiTensorPtrSrc, + cropTensorPtr, + patchTensorPtr); + } + } + + return RPP_SUCCESS; +} diff --git a/src/modules/rppt_tensor_geometric_augmentations.cpp b/src/modules/rppt_tensor_geometric_augmentations.cpp index c7de67b8e..1b1297269 100644 --- a/src/modules/rppt_tensor_geometric_augmentations.cpp +++ b/src/modules/rppt_tensor_geometric_augmentations.cpp @@ -96,6 +96,81 @@ RppStatus rppt_crop_host(RppPtr_t srcPtr, return RPP_SUCCESS; } +/******************** crop_and_patch ********************/ + +RppStatus rppt_crop_and_patch_host(RppPtr_t srcPtr1, + RppPtr_t srcPtr2, + RpptDescPtr srcDescPtr, + RppPtr_t dstPtr, + RpptDescPtr dstDescPtr, + RpptROIPtr roiTensorPtrDst, + RpptROIPtr cropRoi, + RpptROIPtr patchRoi, + RpptRoiType roiType, + rppHandle_t rppHandle) +{ + RppLayoutParams layoutParams = get_layout_params(srcDescPtr->layout, srcDescPtr->c); + + if ((srcDescPtr->dataType == RpptDataType::U8) && (dstDescPtr->dataType == RpptDataType::U8)) + { + crop_and_patch_u8_u8_host_tensor(static_cast(srcPtr1) + srcDescPtr->offsetInBytes, + static_cast(srcPtr2) + srcDescPtr->offsetInBytes, + srcDescPtr, + static_cast(dstPtr) + dstDescPtr->offsetInBytes, + dstDescPtr, + roiTensorPtrDst, + cropRoi, + patchRoi, + roiType, + layoutParams, + rpp::deref(rppHandle)); + } + else if ((srcDescPtr->dataType == RpptDataType::F16) && (dstDescPtr->dataType == RpptDataType::F16)) + { + crop_and_patch_f16_f16_host_tensor((Rpp16f*) (static_cast(srcPtr1) + srcDescPtr->offsetInBytes), + (Rpp16f*) (static_cast(srcPtr2) + srcDescPtr->offsetInBytes), + srcDescPtr, + (Rpp16f*) (static_cast(dstPtr) + dstDescPtr->offsetInBytes), + dstDescPtr, + roiTensorPtrDst, + cropRoi, + patchRoi, + roiType, + layoutParams, + rpp::deref(rppHandle)); + } + else if ((srcDescPtr->dataType == RpptDataType::F32) && (dstDescPtr->dataType == RpptDataType::F32)) + { + crop_and_patch_f32_f32_host_tensor((Rpp32f*) (static_cast(srcPtr1) + srcDescPtr->offsetInBytes), + (Rpp32f*) (static_cast(srcPtr2) + srcDescPtr->offsetInBytes), + srcDescPtr, + (Rpp32f*) (static_cast(dstPtr) + dstDescPtr->offsetInBytes), + dstDescPtr, + roiTensorPtrDst, + cropRoi, + patchRoi, + roiType, + layoutParams, + rpp::deref(rppHandle)); + } + else if ((srcDescPtr->dataType == RpptDataType::I8) && (dstDescPtr->dataType == RpptDataType::I8)) + { + crop_and_patch_i8_i8_host_tensor(static_cast(srcPtr1) + srcDescPtr->offsetInBytes, + static_cast(srcPtr2) + srcDescPtr->offsetInBytes, + srcDescPtr, + static_cast(dstPtr) + dstDescPtr->offsetInBytes, + dstDescPtr, + roiTensorPtrDst, + cropRoi, + patchRoi, + roiType, + layoutParams, + rpp::deref(rppHandle)); + } + + return RPP_SUCCESS; +} + /******************** crop mirror normalize ********************/ RppStatus rppt_crop_mirror_normalize_host(RppPtr_t srcPtr, @@ -1809,6 +1884,76 @@ RppStatus rppt_slice_gpu(RppPtr_t srcPtr, #endif // backend } +RppStatus rppt_crop_and_patch_gpu(RppPtr_t srcPtr1, + RppPtr_t srcPtr2, + RpptDescPtr srcDescPtr, + RppPtr_t dstPtr, + RpptDescPtr dstDescPtr, + RpptROIPtr roiTensorPtrSrc, + RpptROIPtr cropTensorPtr, + RpptROIPtr patchTensorPtr, + RpptRoiType roiType, + rppHandle_t rppHandle) +{ +#ifdef HIP_COMPILE + if ((srcDescPtr->dataType == RpptDataType::U8) && (dstDescPtr->dataType == RpptDataType::U8)) + { + hip_exec_crop_and_patch_tensor(static_cast(srcPtr1) + srcDescPtr->offsetInBytes, + static_cast(srcPtr2) + srcDescPtr->offsetInBytes, + srcDescPtr, + static_cast(dstPtr) + dstDescPtr->offsetInBytes, + dstDescPtr, + roiTensorPtrSrc, + cropTensorPtr, + patchTensorPtr, + roiType, + rpp::deref(rppHandle)); + } + else if ((srcDescPtr->dataType == RpptDataType::F16) && (dstDescPtr->dataType == RpptDataType::F16)) + { + hip_exec_crop_and_patch_tensor(reinterpret_cast(static_cast(srcPtr1)) + srcDescPtr->offsetInBytes, + reinterpret_cast(static_cast(srcPtr2)) + srcDescPtr->offsetInBytes, + srcDescPtr, + reinterpret_cast(static_cast(dstPtr)) + dstDescPtr->offsetInBytes, + dstDescPtr, + roiTensorPtrSrc, + cropTensorPtr, + patchTensorPtr, + roiType, + rpp::deref(rppHandle)); + } + else if ((srcDescPtr->dataType == RpptDataType::F32) && (dstDescPtr->dataType == RpptDataType::F32)) + { + hip_exec_crop_and_patch_tensor(reinterpret_cast(static_cast(srcPtr1)) + srcDescPtr->offsetInBytes, + reinterpret_cast(static_cast(srcPtr2)) + srcDescPtr->offsetInBytes, + srcDescPtr, + reinterpret_cast(static_cast(dstPtr)) + dstDescPtr->offsetInBytes, + dstDescPtr, + roiTensorPtrSrc, + cropTensorPtr, + patchTensorPtr, + roiType, + rpp::deref(rppHandle)); + } + else if ((srcDescPtr->dataType == RpptDataType::I8) && (dstDescPtr->dataType == RpptDataType::I8)) + { + hip_exec_crop_and_patch_tensor(static_cast(srcPtr1) + srcDescPtr->offsetInBytes, + static_cast(srcPtr2) + srcDescPtr->offsetInBytes, + srcDescPtr, + static_cast(dstPtr) + dstDescPtr->offsetInBytes, + dstDescPtr, + roiTensorPtrSrc, + cropTensorPtr, + patchTensorPtr, + roiType, + rpp::deref(rppHandle)); + } + return RPP_SUCCESS; +#elif defined(OCL_COMPILE) + return RPP_ERROR_NOT_IMPLEMENTED; +#endif // backend +} + /******************** flip_voxel ********************/ RppStatus rppt_flip_voxel_gpu(RppPtr_t srcPtr, diff --git a/utilities/test_suite/HIP/Tensor_hip.cpp b/utilities/test_suite/HIP/Tensor_hip.cpp index 2743c4e1f..1ee8dd3b4 100644 --- a/utilities/test_suite/HIP/Tensor_hip.cpp +++ b/utilities/test_suite/HIP/Tensor_hip.cpp @@ -65,7 +65,7 @@ int main(int argc, char **argv) bool additionalParamCase = (testCase == 8 || testCase == 21 || testCase == 23|| testCase == 24 || testCase == 40 || testCase == 41 || testCase == 49 || testCase == 54); bool kernelSizeCase = (testCase == 40 || testCase == 41 || testCase == 49 || testCase == 54); - bool dualInputCase = (testCase == 2 || testCase == 30 || testCase == 61 || testCase == 63 || testCase == 65 || testCase == 68); + bool dualInputCase = (testCase == 2 || testCase == 30 || testCase == 33 || testCase == 61 || testCase == 63 || testCase == 65 || testCase == 68); bool randomOutputCase = (testCase == 84 || testCase == 49 || testCase == 54); bool interpolationTypeCase = (testCase == 21 || testCase == 23 || testCase == 24); bool reductionTypeCase = (testCase == 87 || testCase == 88 || testCase == 89); @@ -346,6 +346,14 @@ int main(int argc, char **argv) if(testCase == 82) CHECK(hipHostMalloc(&roiPtrInputCropRegion, 4 * sizeof(RpptROI))); + RpptROI *cropRoi, *patchRoi; + if(testCase == 33) + { + CHECK(hipHostMalloc(&cropRoi, batchSize * sizeof(RpptROI))); + CHECK(hipHostMalloc(&patchRoi, batchSize * sizeof(RpptROI))); + } + bool invalidROI = (roiList[0] == 0 && roiList[1] == 0 && roiList[2] == 0 && roiList[3] == 0); + Rpp32f *intensity; if(testCase == 46) CHECK(hipHostMalloc(&intensity, batchSize * sizeof(Rpp32f))); @@ -394,7 +402,7 @@ int main(int argc, char **argv) CHECK(hipMemcpy(d_input_second, input_second, inputBufferSize, hipMemcpyHostToDevice)); int roiHeightList[batchSize], roiWidthList[batchSize]; - if(roiList[0] == 0 && roiList[1] == 0 && roiList[2] == 0 && roiList[3] == 0) + if(invalidROI) { for(int i = 0; i < batchSize ; i++) { @@ -645,6 +653,25 @@ int main(int argc, char **argv) break; } + case 33: + { + testCaseName = "crop_and_patch"; + for (i = 0; i < batchSize; i++) + { + cropRoi[i].xywhROI.xy.x = patchRoi[i].xywhROI.xy.x = roiList[0]; + cropRoi[i].xywhROI.xy.y = patchRoi[i].xywhROI.xy.y = roiList[1]; + cropRoi[i].xywhROI.roiWidth = patchRoi[i].xywhROI.roiWidth = roiWidthList[i]; + cropRoi[i].xywhROI.roiHeight = patchRoi[i].xywhROI.roiHeight = roiHeightList[i]; + } + + startWallTime = omp_get_wtime(); + if (inputBitDepth == 0 || inputBitDepth == 1 || inputBitDepth == 2 || inputBitDepth == 5) + rppt_crop_and_patch_gpu(d_input, d_input_second, srcDescPtr, d_output, dstDescPtr, roiTensorPtrSrc, cropRoi, patchRoi, roiTypeSrc, handle); + else + missingFuncFlag = 1; + + break; + } case 34: { testCaseName = "lut"; @@ -1258,6 +1285,11 @@ int main(int argc, char **argv) CHECK(hipHostFree(intensity)); if(testCase == 82) CHECK(hipHostFree(roiPtrInputCropRegion)); + if(testCase == 33) + { + CHECK(hipHostFree(cropRoi)); + CHECK(hipHostFree(patchRoi)); + } if (reductionTypeCase) CHECK(hipHostFree(reductionFuncResultArr)); free(input); diff --git a/utilities/test_suite/HIP/runTests.py b/utilities/test_suite/HIP/runTests.py index 14fb0270a..76fb7d15c 100644 --- a/utilities/test_suite/HIP/runTests.py +++ b/utilities/test_suite/HIP/runTests.py @@ -327,7 +327,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', '46', '54', '61', '63', '65', '68', '70', '80', '82', '83', '84', '85', '86', '87', '88', '89'] +supportedCaseList = ['0', '1', '2', '4', '8', '13', '20', '21', '23', '29', '30', '31', '33', '34', '36', '37', '38', '39', '45', '46', '54', '61', '63', '65', '68', '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 5c9d3c7dc..2a4558401 100644 --- a/utilities/test_suite/HOST/Tensor_host.cpp +++ b/utilities/test_suite/HOST/Tensor_host.cpp @@ -65,7 +65,7 @@ int main(int argc, char **argv) int batchSize = atoi(argv[14]); bool additionalParamCase = (testCase == 8 || testCase == 21 || testCase == 23 || testCase == 24); - bool dualInputCase = (testCase == 2 || testCase == 30 || testCase == 61 || testCase == 63 || testCase == 65 || testCase == 68); + bool dualInputCase = (testCase == 2 || testCase == 30 || testCase == 33 || testCase == 61 || testCase == 63 || testCase == 65 || testCase == 68); bool randomOutputCase = (testCase == 84); bool interpolationTypeCase = (testCase == 21 || testCase == 23 || testCase == 24); bool reductionTypeCase = (testCase == 87 || testCase == 88 || testCase == 89); @@ -334,6 +334,14 @@ int main(int argc, char **argv) } } + RpptROI *cropRoi, *patchRoi; + if(testCase == 33) + { + cropRoi = static_cast(calloc(batchSize, sizeof(RpptROI))); + patchRoi = static_cast(calloc(batchSize, sizeof(RpptROI))); + } + bool invalidROI = (roiList[0] == 0 && roiList[1] == 0 && roiList[2] == 0 && roiList[3] == 0); + // Set the number of threads to be used by OpenMP pragma for RPP batch processing on host. // If numThreads value passed is 0, number of OpenMP threads used by RPP will be set to batch size Rpp32u numThreads = 0; @@ -383,7 +391,7 @@ int main(int argc, char **argv) convert_input_bitdepth(input, input_second, inputu8, inputu8Second, inputBitDepth, ioBufferSize, inputBufferSize, srcDescPtr, dualInputCase, conversionFactor); int roiHeightList[batchSize], roiWidthList[batchSize]; - if(roiList[0] == 0 && roiList[1] == 0 && roiList[2] == 0 && roiList[3] == 0) + if(invalidROI) { for(int i = 0; i < batchSize ; i++) { @@ -595,6 +603,7 @@ int main(int argc, char **argv) } startWallTime = omp_get_wtime(); + startCpuTime = clock(); if (inputBitDepth == 0 || inputBitDepth == 1 || inputBitDepth == 2 || inputBitDepth == 5) rppt_water_host(input, srcDescPtr, output, dstDescPtr, amplX, amplY, freqX, freqY, phaseX, phaseY, roiTensorPtrSrc, roiTypeSrc, handle); else @@ -705,6 +714,27 @@ int main(int argc, char **argv) break; } + case 33: + { + testCaseName = "crop_and_patch"; + + for (i = 0; i < batchSize; i++) + { + cropRoi[i].xywhROI.xy.x = patchRoi[i].xywhROI.xy.x = roiList[0]; + cropRoi[i].xywhROI.xy.y = patchRoi[i].xywhROI.xy.y = roiList[1]; + cropRoi[i].xywhROI.roiWidth = patchRoi[i].xywhROI.roiWidth = roiWidthList[i]; + cropRoi[i].xywhROI.roiHeight = patchRoi[i].xywhROI.roiHeight = roiHeightList[i]; + } + + startWallTime = omp_get_wtime(); + startCpuTime = clock(); + if (inputBitDepth == 0 || inputBitDepth == 1 || inputBitDepth == 2 || inputBitDepth == 5) + rppt_crop_and_patch_host(input, input_second, srcDescPtr, output, dstDescPtr, roiTensorPtrDst, cropRoi, patchRoi, roiTypeSrc, handle); + else + missingFuncFlag = 1; + + break; + } case 37: { testCaseName = "crop"; @@ -1297,5 +1327,10 @@ int main(int argc, char **argv) free(output); if(reductionTypeCase) free(reductionFuncResultArr); + if(testCase == 33) + { + free(cropRoi); + free(patchRoi); + } return 0; } diff --git a/utilities/test_suite/HOST/runTests.py b/utilities/test_suite/HOST/runTests.py index 00ae0a48e..ddde7db9c 100644 --- a/utilities/test_suite/HOST/runTests.py +++ b/utilities/test_suite/HOST/runTests.py @@ -283,7 +283,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', '46', '54', '61', '63', '65', '68', '70', '80', '81', '82', '83', '84', '85', '86', '87', '88', '89'] +supportedCaseList = ['0', '1', '2', '4', '8', '13', '20', '21', '23', '29', '30', '31', '33', '34', '36', '37', '38', '39', '45', '46', '54', '61', '63', '65', '68', '70', '80', '81', '82', '83', '84', '85', '86', '87', '88', '89'] print("\n\n\n\n\n") print("##########################################################################################") diff --git a/utilities/test_suite/REFERENCE_OUTPUT/crop_and_patch/crop_and_patch_u8_Tensor.bin b/utilities/test_suite/REFERENCE_OUTPUT/crop_and_patch/crop_and_patch_u8_Tensor.bin new file mode 100644 index 0000000000000000000000000000000000000000..3bfa07bd0cd930ce1e476373253c25016b509390 GIT binary patch literal 273600 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 augmentationMap = {29, "water"}, {30, "non_linear_blend"}, {31, "color_cast"}, + {33, "crop_and_patch"}, {34, "lut"}, {36, "color_twist"}, {37, "crop"},