1
0
mirror of https://github.com/opencv/opencv.git synced 2026-07-21 19:33:03 +04:00

Merge pull request #29340 from amd:fast_minmax_simd

core: Optimize minMaxLoc
This commit is contained in:
Alexander Smorkalov
2026-06-23 17:16:54 +03:00
committed by GitHub
4 changed files with 1012 additions and 1368 deletions
+1
View File
@@ -10,6 +10,7 @@ ocv_add_dispatched_file(has_non_zero SSE2 AVX2 LASX )
ocv_add_dispatched_file(matmul SSE2 SSE4_1 AVX2 AVX512_SKX NEON_DOTPROD LASX)
ocv_add_dispatched_file(mean SSE2 AVX2 AVX512_SKX AVX512_ICL LASX)
ocv_add_dispatched_file(merge SSE2 AVX2 LASX)
ocv_add_dispatched_file(minmax SSE2 SSE4_1 AVX2 AVX512_SKX AVX512_ICL LASX)
ocv_add_dispatched_file(split SSE2 AVX2 LASX)
ocv_add_dispatched_file(sum SSE2 AVX2 LASX)
ocv_add_dispatched_file(reduce SSE2 SSSE3 AVX2 NEON_DOTPROD)
File diff suppressed because it is too large Load Diff
+540
View File
@@ -0,0 +1,540 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html
// Copyright (C) 2026, Advanced Micro Devices, Inc., all rights reserved.
#include "precomp.hpp"
#include "opencl_kernels_core.hpp"
#include "stat.hpp"
#include "opencv2/core/detail/dispatch_helper.impl.hpp"
#include <algorithm>
#include "minmax.simd.hpp"
#include "minmax.simd_declarations.hpp" // defines CV_CPU_DISPATCH_MODES_ALL=AVX2,...,BASELINE based on CMakeLists.txt content
/****************************************************************************************\
* minMaxLoc *
\****************************************************************************************/
namespace cv
{
static MinMaxIdxFunc getMinmaxTab(int depth)
{
CV_INSTRUMENT_REGION();
CV_CPU_DISPATCH(getMinmaxTab, (depth),
CV_CPU_DISPATCH_MODES_ALL);
}
// The function expects 1-based indexing for ofs
// Zero is treated as invalid offset (not found)
static void ofs2idx(const Mat& a, size_t ofs, int* idx)
{
int i, d = a.dims;
if( ofs > 0 )
{
ofs--;
for( i = d-1; i >= 0; i-- )
{
int sz = a.size[i];
idx[i] = (int)(ofs % sz);
ofs /= sz;
}
}
else
{
for( i = d-1; i >= 0; i-- )
idx[i] = -1;
}
}
#ifdef HAVE_OPENCL
#define MINMAX_STRUCT_ALIGNMENT 8 // sizeof double
template <typename T>
void getMinMaxRes(const Mat & db, double * minVal, double * maxVal,
int* minLoc, int* maxLoc,
int groupnum, int cols, double * maxVal2)
{
uint index_max = std::numeric_limits<uint>::max();
T minval = std::numeric_limits<T>::max();
T maxval = std::numeric_limits<T>::min() > 0 ? -std::numeric_limits<T>::max() : std::numeric_limits<T>::min(), maxval2 = maxval;
uint minloc = index_max, maxloc = index_max;
size_t index = 0;
const T * minptr = NULL, * maxptr = NULL, * maxptr2 = NULL;
const uint * minlocptr = NULL, * maxlocptr = NULL;
if (minVal || minLoc)
{
minptr = db.ptr<T>();
index += sizeof(T) * groupnum;
index = alignSize(index, MINMAX_STRUCT_ALIGNMENT);
}
if (maxVal || maxLoc)
{
maxptr = (const T *)(db.ptr() + index);
index += sizeof(T) * groupnum;
index = alignSize(index, MINMAX_STRUCT_ALIGNMENT);
}
if (minLoc)
{
minlocptr = (const uint *)(db.ptr() + index);
index += sizeof(uint) * groupnum;
index = alignSize(index, MINMAX_STRUCT_ALIGNMENT);
}
if (maxLoc)
{
maxlocptr = (const uint *)(db.ptr() + index);
index += sizeof(uint) * groupnum;
index = alignSize(index, MINMAX_STRUCT_ALIGNMENT);
}
if (maxVal2)
maxptr2 = (const T *)(db.ptr() + index);
for (int i = 0; i < groupnum; i++)
{
if (minptr && minptr[i] <= minval)
{
if (minptr[i] == minval)
{
if (minlocptr)
minloc = std::min(minlocptr[i], minloc);
}
else
{
if (minlocptr)
minloc = minlocptr[i];
minval = minptr[i];
}
}
if (maxptr && maxptr[i] >= maxval)
{
if (maxptr[i] == maxval)
{
if (maxlocptr)
maxloc = std::min(maxlocptr[i], maxloc);
}
else
{
if (maxlocptr)
maxloc = maxlocptr[i];
maxval = maxptr[i];
}
}
if (maxptr2 && maxptr2[i] > maxval2)
maxval2 = maxptr2[i];
}
bool zero_mask = (minLoc && minloc == index_max) ||
(maxLoc && maxloc == index_max);
if (minVal)
*minVal = zero_mask ? 0 : (double)minval;
if (maxVal)
*maxVal = zero_mask ? 0 : (double)maxval;
if (maxVal2)
*maxVal2 = zero_mask ? 0 : (double)maxval2;
if (minLoc)
{
minLoc[0] = zero_mask ? -1 : minloc / cols;
minLoc[1] = zero_mask ? -1 : minloc % cols;
}
if (maxLoc)
{
maxLoc[0] = zero_mask ? -1 : maxloc / cols;
maxLoc[1] = zero_mask ? -1 : maxloc % cols;
}
}
typedef void (*getMinMaxResFunc)(const Mat & db, double * minVal, double * maxVal,
int * minLoc, int *maxLoc, int gropunum, int cols, double * maxVal2);
bool ocl_minMaxIdx( InputArray _src, double* minVal, double* maxVal, int* minLoc, int* maxLoc, InputArray _mask,
int ddepth, bool absValues, InputArray _src2, double * maxVal2)
{
const ocl::Device & dev = ocl::Device::getDefault();
#ifdef __ANDROID__
if (dev.isNVidia())
return false;
#endif
if (dev.deviceVersionMajor() == 1 && dev.deviceVersionMinor() < 2)
{
// 'static' storage class specifier used by "minmaxloc" is available from OpenCL 1.2+ only
return false;
}
bool doubleSupport = dev.doubleFPConfig() > 0, haveMask = !_mask.empty(),
haveSrc2 = _src2.kind() != _InputArray::NONE;
int type = _src.type(), depth = CV_MAT_DEPTH(type), cn = CV_MAT_CN(type),
kercn = haveMask ? cn : std::min(4, ocl::predictOptimalVectorWidth(_src, _src2));
if (depth >= CV_16F)
return false;
// disabled following modes since it occasionally fails on AMD devices (e.g. A10-6800K, sep. 2014)
if ((haveMask || type == CV_32FC1) && dev.isAMD())
return false;
CV_Assert( (cn == 1 && (!haveMask || _mask.type() == CV_8U)) ||
(cn >= 1 && !minLoc && !maxLoc) );
if (ddepth < 0)
ddepth = depth;
CV_Assert(!haveSrc2 || _src2.type() == type);
if (depth == CV_32S)
return false;
if ((depth == CV_64F || ddepth == CV_64F) && !doubleSupport)
return false;
int groupnum = dev.maxComputeUnits();
size_t wgs = dev.maxWorkGroupSize();
int wgs2_aligned = 1;
while (wgs2_aligned < (int)wgs)
wgs2_aligned <<= 1;
wgs2_aligned >>= 1;
bool needMinVal = minVal || minLoc, needMinLoc = minLoc != NULL,
needMaxVal = maxVal || maxLoc, needMaxLoc = maxLoc != NULL;
// in case of mask we must know whether mask is filled with zeros or not
// so let's calculate min or max location, if it's undefined, so mask is zeros
if (!(needMaxLoc || needMinLoc) && haveMask)
{
if (needMinVal)
needMinLoc = true;
else
needMaxLoc = true;
}
char cvt[2][50];
String opts = format("-D DEPTH_%d -D srcT1=%s%s -D WGS=%d -D srcT=%s"
" -D WGS2_ALIGNED=%d%s%s%s -D kercn=%d%s%s%s%s"
" -D dstT1=%s -D dstT=%s -D convertToDT=%s%s%s%s%s -D wdepth=%d -D convertFromU=%s"
" -D MINMAX_STRUCT_ALIGNMENT=%d",
depth, ocl::typeToStr(depth), haveMask ? " -D HAVE_MASK" : "", (int)wgs,
ocl::typeToStr(CV_MAKE_TYPE(depth, kercn)), wgs2_aligned,
doubleSupport ? " -D DOUBLE_SUPPORT" : "",
_src.isContinuous() ? " -D HAVE_SRC_CONT" : "",
_mask.isContinuous() ? " -D HAVE_MASK_CONT" : "", kercn,
needMinVal ? " -D NEED_MINVAL" : "", needMaxVal ? " -D NEED_MAXVAL" : "",
needMinLoc ? " -D NEED_MINLOC" : "", needMaxLoc ? " -D NEED_MAXLOC" : "",
ocl::typeToStr(ddepth), ocl::typeToStr(CV_MAKE_TYPE(ddepth, kercn)),
ocl::convertTypeStr(depth, ddepth, kercn, cvt[0], sizeof(cvt[0])),
absValues ? " -D OP_ABS" : "",
haveSrc2 ? " -D HAVE_SRC2" : "", maxVal2 ? " -D OP_CALC2" : "",
haveSrc2 && _src2.isContinuous() ? " -D HAVE_SRC2_CONT" : "", ddepth,
depth <= CV_32S && ddepth == CV_32S ? ocl::convertTypeStr(CV_8U, ddepth, kercn, cvt[1], sizeof(cvt[1])) : "noconvert",
MINMAX_STRUCT_ALIGNMENT);
ocl::Kernel k("minmaxloc", ocl::core::minmaxloc_oclsrc, opts);
if (k.empty())
return false;
int esz = CV_ELEM_SIZE(ddepth), esz32s = CV_ELEM_SIZE1(CV_32S),
dbsize = groupnum * ((needMinVal ? esz : 0) + (needMaxVal ? esz : 0) +
(needMinLoc ? esz32s : 0) + (needMaxLoc ? esz32s : 0) +
(maxVal2 ? esz : 0))
+ 5 * MINMAX_STRUCT_ALIGNMENT;
UMat src = _src.getUMat(), src2 = _src2.getUMat(), db(1, dbsize, CV_8UC1), mask = _mask.getUMat();
if (cn > 1 && !haveMask)
{
src = src.reshape(1);
src2 = src2.reshape(1);
}
if (haveSrc2)
{
if (!haveMask)
k.args(ocl::KernelArg::ReadOnlyNoSize(src), src.cols, (int)src.total(),
groupnum, ocl::KernelArg::PtrWriteOnly(db), ocl::KernelArg::ReadOnlyNoSize(src2));
else
k.args(ocl::KernelArg::ReadOnlyNoSize(src), src.cols, (int)src.total(),
groupnum, ocl::KernelArg::PtrWriteOnly(db), ocl::KernelArg::ReadOnlyNoSize(mask),
ocl::KernelArg::ReadOnlyNoSize(src2));
}
else
{
if (!haveMask)
k.args(ocl::KernelArg::ReadOnlyNoSize(src), src.cols, (int)src.total(),
groupnum, ocl::KernelArg::PtrWriteOnly(db));
else
k.args(ocl::KernelArg::ReadOnlyNoSize(src), src.cols, (int)src.total(),
groupnum, ocl::KernelArg::PtrWriteOnly(db), ocl::KernelArg::ReadOnlyNoSize(mask));
}
size_t globalsize = groupnum * wgs;
if (!k.run(1, &globalsize, &wgs, true))
return false;
static const getMinMaxResFunc functab[7] =
{
getMinMaxRes<uchar>,
getMinMaxRes<char>,
getMinMaxRes<ushort>,
getMinMaxRes<short>,
getMinMaxRes<int>,
getMinMaxRes<float>,
getMinMaxRes<double>
};
CV_Assert(ddepth <= CV_64F);
getMinMaxResFunc func = functab[ddepth];
int locTemp[2];
func(db.getMat(ACCESS_READ), minVal, maxVal,
needMinLoc ? minLoc ? minLoc : locTemp : minLoc,
needMaxLoc ? maxLoc ? maxLoc : locTemp : maxLoc,
groupnum, src.cols, maxVal2);
return true;
}
#endif
}
void cv::minMaxIdx(InputArray _src, double* minVal,
double* maxVal, int* minIdx, int* maxIdx,
InputArray _mask)
{
CV_INSTRUMENT_REGION();
int type = _src.type(), depth = CV_MAT_DEPTH(type), cn = CV_MAT_CN(type);
CV_Assert( (cn == 1 && (_mask.empty() || _mask.type() == CV_8U)) ||
(cn > 1 && _mask.empty() && !minIdx && !maxIdx) );
CV_OCL_RUN(OCL_PERFORMANCE_CHECK(_src.isUMat()) && _src.dims() <= 2 && (_mask.empty() || _src.size() == _mask.size()),
ocl_minMaxIdx(_src, minVal, maxVal, minIdx, maxIdx, _mask))
Mat src = _src.getMat(), mask = _mask.getMat();
if (src.dims <= 2)
{
if ((size_t)src.step == (size_t)mask.step || mask.empty())
{
CALL_HAL(minMaxIdx, cv_hal_minMaxIdx, src.data, src.step, src.cols*cn, src.rows,
src.depth(), minVal, maxVal, minIdx, maxIdx, mask.data);
}
CALL_HAL(minMaxIdxMaskStep, cv_hal_minMaxIdxMaskStep, src.data, src.step, src.cols*cn, src.rows,
src.depth(), minVal, maxVal, minIdx, maxIdx, mask.data, mask.step);
}
else if (src.isContinuous() && (mask.isContinuous() || mask.empty()))
{
int res = cv_hal_minMaxIdx(src.data, 0, (int)src.total()*cn, 1, src.depth(),
minVal, maxVal, minIdx, maxIdx, mask.data);
if (res == CV_HAL_ERROR_OK)
{
// minIdx[0] and minIdx[0] are always 0 for "flatten" version
if (minIdx)
ofs2idx(src, minIdx[1]+1, minIdx);
if (maxIdx)
ofs2idx(src, maxIdx[1]+1, maxIdx);
return;
}
else if (res != CV_HAL_ERROR_NOT_IMPLEMENTED)
{
CV_Error_(cv::Error::StsInternal,
("HAL implementation minMaxIdx ==> " CVAUX_STR(cv_hal_minMaxIdx) " returned %d (0x%08x)", res, res));
}
}
MinMaxIdxFunc func = getMinmaxTab(depth);
CV_Assert( func != 0 );
const Mat* arrays[] = {&src, &mask, 0};
uchar* ptrs[2] = {};
NAryMatIterator it(arrays, ptrs);
size_t minidx = 0, maxidx = 0;
int iminval = INT_MAX, imaxval = INT_MIN;
float fminval = std::numeric_limits<float>::infinity(), fmaxval = -fminval;
double dminval = std::numeric_limits<double>::infinity(), dmaxval = -dminval;
size_t startidx = 1;
void *minval = &iminval, *maxval = &imaxval;
int planeSize = (int)it.size*cn;
if( depth == CV_32F )
minval = &fminval, maxval = &fmaxval;
else if( depth == CV_64F )
minval = &dminval, maxval = &dmaxval;
for( size_t i = 0; i < it.nplanes; i++, ++it, startidx += planeSize )
func( ptrs[0], ptrs[1], minval, maxval, &minidx, &maxidx, planeSize, startidx );
if (!src.empty() && mask.empty())
{
if( minidx == 0 )
minidx = 1;
if( maxidx == 0 )
maxidx = 1;
}
if( minidx == 0 )
dminval = dmaxval = 0;
else if( depth == CV_32F )
dminval = fminval, dmaxval = fmaxval;
else if( depth <= CV_32S )
dminval = iminval, dmaxval = imaxval;
if( minVal )
*minVal = dminval;
if( maxVal )
*maxVal = dmaxval;
if( minIdx )
ofs2idx(src, minidx, minIdx);
if( maxIdx )
ofs2idx(src, maxidx, maxIdx);
}
void cv::minMaxLoc( InputArray _img, double* minVal, double* maxVal,
Point* minLoc, Point* maxLoc, InputArray mask )
{
CV_INSTRUMENT_REGION();
int dims = _img.dims();
CV_CheckLE(dims, 2, "");
minMaxIdx(_img, minVal, maxVal, (int*)minLoc, (int*)maxLoc, mask);
if( minLoc )
{
if (dims == 2)
std::swap(minLoc->x, minLoc->y);
else
minLoc->y = 0;
}
if( maxLoc )
{
if (dims == 2)
std::swap(maxLoc->x, maxLoc->y);
else
maxLoc->y = 0;
}
}
enum class ReduceMode
{
FIRST_MIN = 0, //!< get index of first min occurrence
LAST_MIN = 1, //!< get index of last min occurrence
FIRST_MAX = 2, //!< get index of first max occurrence
LAST_MAX = 3, //!< get index of last max occurrence
};
template <typename T>
struct reduceMinMaxImpl
{
void operator()(const cv::Mat& src, cv::Mat& dst, ReduceMode mode, const int axis) const
{
switch(mode)
{
case ReduceMode::FIRST_MIN:
reduceMinMaxApply<std::less>(src, dst, axis);
break;
case ReduceMode::LAST_MIN:
reduceMinMaxApply<std::less_equal>(src, dst, axis);
break;
case ReduceMode::FIRST_MAX:
reduceMinMaxApply<std::greater>(src, dst, axis);
break;
case ReduceMode::LAST_MAX:
reduceMinMaxApply<std::greater_equal>(src, dst, axis);
break;
}
}
template <template<class> class Cmp>
static void reduceMinMaxApply(const cv::Mat& src, cv::Mat& dst, const int axis)
{
Cmp<T> cmp;
const auto *src_ptr = src.ptr<T>();
auto *dst_ptr = dst.ptr<int32_t>();
const size_t outer_size = src.total(0, axis);
const auto mid_size = static_cast<size_t>(src.size[axis]);
const size_t outer_step = src.total(axis);
const size_t dst_step = dst.total(axis);
const size_t mid_step = src.total(axis + 1);
for (size_t outer = 0; outer < outer_size; ++outer)
{
const size_t outer_offset = outer * outer_step;
const size_t dst_offset = outer * dst_step;
for (size_t mid = 0; mid != mid_size; ++mid)
{
const size_t src_offset = outer_offset + mid * mid_step;
for (size_t inner = 0; inner < mid_step; inner++)
{
int32_t& index = dst_ptr[dst_offset + inner];
const size_t prev = outer_offset + index * mid_step + inner;
const size_t curr = src_offset + inner;
if (cmp(src_ptr[curr], src_ptr[prev]))
{
index = static_cast<int32_t>(mid);
}
}
}
}
}
};
static void reduceMinMax(cv::InputArray src, cv::OutputArray dst, ReduceMode mode, int axis)
{
CV_INSTRUMENT_REGION();
cv::Mat srcMat = src.getMat();
axis = (axis + srcMat.dims) % srcMat.dims;
CV_Assert(srcMat.channels() == 1 && axis >= 0 && axis < srcMat.dims);
cv::AutoBuffer<int> sizes(srcMat.dims);
std::copy(srcMat.size.p, srcMat.size.p + srcMat.dims, sizes.begin());
sizes[axis] = 1;
dst.create(srcMat.dims, sizes.data(), CV_32SC1); // indices
cv::Mat dstMat = dst.getMat();
dstMat.setTo(cv::Scalar::all(0));
if (!srcMat.isContinuous())
{
srcMat = srcMat.clone();
}
bool needs_copy = !dstMat.isContinuous();
if (needs_copy)
{
dstMat = dstMat.clone();
}
cv::detail::depthDispatch<reduceMinMaxImpl>(srcMat.depth(), srcMat, dstMat, mode, axis);
if (needs_copy)
{
dstMat.copyTo(dst);
}
}
void cv::reduceArgMin(InputArray src, OutputArray dst, int axis, bool lastIndex)
{
reduceMinMax(src, dst, lastIndex ? ReduceMode::LAST_MIN : ReduceMode::FIRST_MIN, axis);
}
void cv::reduceArgMax(InputArray src, OutputArray dst, int axis, bool lastIndex)
{
reduceMinMax(src, dst, lastIndex ? ReduceMode::LAST_MAX : ReduceMode::FIRST_MAX, axis);
}
+471
View File
@@ -0,0 +1,471 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html
// Copyright (C) 2026, Advanced Micro Devices, Inc., all rights reserved.
#include "precomp.hpp"
#include "stat.hpp"
#include <algorithm>
namespace cv {
typedef void (*MinMaxIdxFunc)(const void*, const uchar*, void*, void*, size_t*, size_t*, int, size_t);
CV_CPU_OPTIMIZATION_NAMESPACE_BEGIN
MinMaxIdxFunc getMinmaxTab(int depth);
#ifndef CV_CPU_OPTIMIZATION_DECLARATIONS_ONLY
template<typename T, typename WT> static void
minMaxIdx_( const T* src, const uchar* mask, WT* _minVal, WT* _maxVal,
size_t* _minIdx, size_t* _maxIdx, int len, size_t startIdx )
{
WT minVal = *_minVal, maxVal = *_maxVal;
size_t minIdx = *_minIdx, maxIdx = *_maxIdx;
if( !mask )
{
for( int i = 0; i < len; i++ )
{
T val = src[i];
if( val < minVal )
{
minVal = val;
minIdx = startIdx + i;
}
if( val > maxVal )
{
maxVal = val;
maxIdx = startIdx + i;
}
}
}
else
{
for( int i = 0; i < len; i++ )
{
T val = src[i];
if( mask[i] && val < minVal )
{
minVal = val;
minIdx = startIdx + i;
}
if( mask[i] && val > maxVal )
{
maxVal = val;
maxIdx = startIdx + i;
}
}
}
*_minIdx = minIdx;
*_maxIdx = maxIdx;
*_minVal = minVal;
*_maxVal = maxVal;
}
#if (CV_SIMD || CV_SIMD_SCALABLE)
template<typename T, typename WT> CV_ALWAYS_INLINE void
minMaxIdx_init( const T* src, const uchar* mask, WT* minval, WT* maxval,
size_t* minidx, size_t* maxidx, WT &minVal, WT &maxVal,
size_t &minIdx, size_t &maxIdx, const WT minInit, const WT maxInit,
const int nlanes, int len, size_t startidx, int &j, int &len0 )
{
len0 = len & -nlanes;
j = 0;
minVal = *minval, maxVal = *maxval;
minIdx = *minidx, maxIdx = *maxidx;
// To handle start values out of range
if ( minVal < minInit || maxVal < minInit || minVal > maxInit || maxVal > maxInit )
{
uchar done = 0x00;
for ( ; (j < len) && (done != 0x03); j++ )
{
if ( !mask || mask[j] ) {
T val = src[j];
if ( val < minVal )
{
minVal = val;
minIdx = startidx + j;
done |= 0x01;
}
if ( val > maxVal )
{
maxVal = val;
maxIdx = startidx + j;
done |= 0x02;
}
}
}
len0 = j + ((len - j) & -nlanes);
}
}
template<typename T, typename WT> CV_ALWAYS_INLINE void
minMaxIdx_finish( const T* src, const uchar* mask, WT* minval, WT* maxval,
size_t* minidx, size_t* maxidx, WT minVal, WT maxVal,
size_t minIdx, size_t maxIdx, int len, size_t startidx,
int j )
{
for ( ; j < len ; j++ )
{
if ( !mask || mask[j] )
{
T val = src[j];
if ( val < minVal )
{
minVal = val;
minIdx = startidx + j;
}
if ( val > maxVal )
{
maxVal = val;
maxIdx = startidx + j;
}
}
}
*minidx = minIdx;
*maxidx = maxIdx;
*minval = minVal;
*maxval = maxVal;
}
//============================================================================
// Templated SIMD core (universal intrinsics) shared by every depth. The
// per-depth wrappers below only select the value/index vector + result types.
//============================================================================
// Fast single-instruction broadcast into any universal vector (value or index).
template<typename V> static inline V mm_set(typename VTraits<V>::lane_type v);
template<> inline v_uint8 mm_set<v_uint8 >(uchar v) { return vx_setall_u8(v); }
template<> inline v_int8 mm_set<v_int8 >(schar v) { return vx_setall_s8(v); }
template<> inline v_uint16 mm_set<v_uint16 >(ushort v) { return vx_setall_u16(v); }
template<> inline v_int16 mm_set<v_int16 >(short v) { return vx_setall_s16(v); }
template<> inline v_uint32 mm_set<v_uint32 >(uint v) { return vx_setall_u32(v); }
template<> inline v_int32 mm_set<v_int32 >(int v) { return vx_setall_s32(v); }
template<> inline v_float32 mm_set<v_float32>(float v) { return vx_setall_f32(v); }
#if (CV_SIMD_64F || CV_SIMD_SCALABLE_64F)
template<> inline v_uint64 mm_set<v_uint64 >(uint64_t v) { return vx_setall_u64(v); }
template<> inline v_float64 mm_set<v_float64>(double v) { return vx_setall_f64(v); }
#endif
// Reinterpret a value-domain comparison mask to the matching unsigned index vector.
static inline v_uint8 mm_idxmask(const v_uint8& m) { return m; }
static inline v_uint8 mm_idxmask(const v_int8& m) { return v_reinterpret_as_u8(m); }
static inline v_uint16 mm_idxmask(const v_uint16& m) { return m; }
static inline v_uint16 mm_idxmask(const v_int16& m) { return v_reinterpret_as_u16(m); }
static inline v_uint32 mm_idxmask(const v_int32& m) { return v_reinterpret_as_u32(m); }
static inline v_uint32 mm_idxmask(const v_float32& m) { return v_reinterpret_as_u32(m); }
#if (CV_SIMD_64F || CV_SIMD_SCALABLE_64F)
static inline v_uint64 mm_idxmask(const v_float64& m) { return v_reinterpret_as_u64(m); }
#endif
// Blend for index vectors (universal API has no v_select for u64).
template<typename IT> static inline IT mm_sel(const IT& mask, const IT& a, const IT& b)
{ return v_xor(b, v_and(v_xor(a, b), mask)); }
// Value reduce-min / reduce-max (f64 has no universal v_reduce_*).
static inline int mm_vmin(const v_uint8& v){ return v_reduce_min(v); }
static inline int mm_vmin(const v_int8& v){ return v_reduce_min(v); }
static inline int mm_vmin(const v_uint16& v){ return v_reduce_min(v); }
static inline int mm_vmin(const v_int16& v){ return v_reduce_min(v); }
static inline int mm_vmin(const v_int32& v){ return v_reduce_min(v); }
static inline float mm_vmin(const v_float32&v){ return v_reduce_min(v); }
static inline int mm_vmax(const v_uint8& v){ return v_reduce_max(v); }
static inline int mm_vmax(const v_int8& v){ return v_reduce_max(v); }
static inline int mm_vmax(const v_uint16& v){ return v_reduce_max(v); }
static inline int mm_vmax(const v_int16& v){ return v_reduce_max(v); }
static inline int mm_vmax(const v_int32& v){ return v_reduce_max(v); }
static inline float mm_vmax(const v_float32&v){ return v_reduce_max(v); }
#if (CV_SIMD_64F || CV_SIMD_SCALABLE_64F)
static inline double mm_vmin(const v_float64& v)
{ double b[VTraits<v_float64>::max_nlanes]; v_store(b, v); double r=b[0]; const int n=VTraits<v_float64>::vlanes(); for(int i=1;i<n;i++) if(b[i]<r) r=b[i]; return r; }
static inline double mm_vmax(const v_float64& v)
{ double b[VTraits<v_float64>::max_nlanes]; v_store(b, v); double r=b[0]; const int n=VTraits<v_float64>::vlanes(); for(int i=1;i<n;i++) if(b[i]>r) r=b[i]; return r; }
#endif
// Index reduce-min (u64 has no universal v_reduce_min).
static inline unsigned mm_imin(const v_uint8& v){ return v_reduce_min(v); }
static inline unsigned mm_imin(const v_uint16& v){ return v_reduce_min(v); }
static inline unsigned mm_imin(const v_uint32& v){ return v_reduce_min(v); }
#if (CV_SIMD_64F || CV_SIMD_SCALABLE_64F)
static inline uint64_t mm_imin(const v_uint64& v)
{ uint64_t b[VTraits<v_uint64>::max_nlanes]; v_store(b, v); uint64_t r=b[0]; const int n=VTraits<v_uint64>::vlanes(); for(int i=1;i<n;i++) if(b[i]<r) r=b[i]; return r; }
#endif
// Active-lane mask in the *value* vector domain, from a uchar* mask.
template<typename VT> static inline VT mm_active(const uchar* mask, int k, int nlanes);
template<> inline v_uint8 mm_active<v_uint8 >(const uchar* mask, int k, int)
{ return v_ne(vx_load(mask + k), vx_setzero_u8()); }
template<> inline v_int8 mm_active<v_int8 >(const uchar* mask, int k, int)
{ return v_reinterpret_as_s8(v_ne(vx_load(mask + k), vx_setzero_u8())); }
template<> inline v_uint16 mm_active<v_uint16>(const uchar* mask, int k, int)
{ return v_ne(vx_load_expand(mask + k), vx_setzero_u16()); }
template<> inline v_int16 mm_active<v_int16 >(const uchar* mask, int k, int)
{ return v_reinterpret_as_s16(v_ne(vx_load_expand(mask + k), vx_setzero_u16())); }
template<> inline v_int32 mm_active<v_int32 >(const uchar* mask, int k, int nlanes)
{ uint32_t b[VTraits<v_uint32>::max_nlanes]; for(int t=0;t<nlanes;t++) b[t]=mask[k+t]?~0u:0u; return v_reinterpret_as_s32(vx_load(b)); }
template<> inline v_float32 mm_active<v_float32>(const uchar* mask, int k, int nlanes)
{ uint32_t b[VTraits<v_uint32>::max_nlanes]; for(int t=0;t<nlanes;t++) b[t]=mask[k+t]?~0u:0u; return v_reinterpret_as_f32(vx_load(b)); }
#if (CV_SIMD_64F || CV_SIMD_SCALABLE_64F)
template<> inline v_float64 mm_active<v_float64>(const uchar* mask, int k, int nlanes)
{ uint64_t b[VTraits<v_uint64>::max_nlanes]; for(int t=0;t<nlanes;t++) b[t]=mask[k+t]?~0ull:0ull; return v_reinterpret_as_f64(vx_load(b)); }
#endif
// Fold one accumulator stream's (value,index) extremum into the running result.
// Scalar tie-break (smaller index wins) lets several independent streams combine
// correctly without any vector index comparison (works for u64 indices too).
template<typename VT, typename IT, typename WT>
static inline void mm_fold_min(const VT& valMin, const IT& idxMin, const IT& none,
size_t delta, WT& minVal, size_t& minIdx)
{
if ( v_check_any(v_ne(idxMin, none)) )
{
WT cv = (WT)mm_vmin(valMin);
IT sel = mm_sel(mm_idxmask(v_eq(mm_set<VT>((typename VTraits<VT>::lane_type)cv), valMin)), idxMin, none);
size_t ci = (size_t)mm_imin(sel) + delta;
if ( cv < minVal || (cv == minVal && ci < minIdx) ) { minVal = cv; minIdx = ci; }
}
}
template<typename VT, typename IT, typename WT>
static inline void mm_fold_max(const VT& valMax, const IT& idxMax, const IT& none,
size_t delta, WT& maxVal, size_t& maxIdx)
{
if ( v_check_any(v_ne(idxMax, none)) )
{
WT cv = (WT)mm_vmax(valMax);
IT sel = mm_sel(mm_idxmask(v_eq(mm_set<VT>((typename VTraits<VT>::lane_type)cv), valMax)), idxMax, none);
size_t ci = (size_t)mm_imin(sel) + delta;
if ( cv > maxVal || (cv == maxVal && ci < maxIdx) ) { maxVal = cv; maxIdx = ci; }
}
}
// IST = unsigned index lane type (uchar / ushort / uint / uint64_t).
template<typename T, typename VT, typename IT, typename IST, typename WT>
static void minMaxIdx_simd_(const T* src, const uchar* mask, WT* minval, WT* maxval,
size_t* minidx, size_t* maxidx, int len, size_t startidx,
WT minInit, WT maxInit)
{
const int nlanes = VTraits<VT>::vlanes();
if ( len >= nlanes )
{
int j, len0;
WT minVal, maxVal;
size_t minIdx, maxIdx;
minMaxIdx_init( src, mask, minval, maxval, minidx, maxidx, minVal, maxVal, minIdx, maxIdx,
minInit, maxInit, nlanes, len, startidx, j, len0 );
if ( j <= len0 - nlanes )
{
IST idxbuf[VTraits<IT>::max_nlanes];
for ( int t = 0; t < nlanes; t++ ) idxbuf[t] = (IST)t;
const IT idxStart = vx_load(idxbuf);
const IT inc = mm_set<IT>((IST)nlanes);
const IT none = mm_set<IT>((IST)~(IST)0);
// Reduce before the per-lane index could reach the 'none' sentinel.
// For >= 32-bit indices (len <= INT_MAX) one block covers everything.
const int64_t idxcap = (sizeof(IST) <= 2) ? (((int64_t)1 << (8 * (int)sizeof(IST))) - 1) : (int64_t)INT_MAX;
const int blockStep = (int)((idxcap / nlanes) * nlanes);
const IT inc2 = mm_set<IT>((IST)(nlanes * 2));
do
{
// Two independent accumulator streams break the serial
// min/max + index-select dependency chain so the CPU can
// overlap iterations (latency-bound loop). Stream 0 covers the
// even vector slots, stream 1 the odd ones; results are merged
// by mm_fold_*. (2 streams is the sweet spot: 4 spills the 16
// YMM regs on AVX2 and gives no gain on AVX-512.) The masked
// path keeps a single stream (stream 0).
VT vMin0 = mm_set<VT>((T)minVal), vMin1 = vMin0;
VT vMax0 = mm_set<VT>((T)maxVal), vMax1 = vMax0;
IT idx0 = idxStart, idx1 = v_add(idxStart, inc);
IT iMin0 = none, iMin1 = none, iMax0 = none, iMax1 = none;
int k = j;
size_t delta = startidx + j;
// 64-bit math: blockStep can be ~INT_MAX (32/64-bit indices), so
// j + blockStep would overflow int once j advances past the start.
const int limit = (int)std::min<int64_t>((int64_t)len0, (int64_t)j + (int64_t)blockStep);
if ( !mask )
{
// Dual stream only for >= 16-bit indices: 8-bit forces tiny
// reduce-blocks (u8 index < 256), where extra per-block folds
// outweigh the dependency-break benefit.
for ( ; (sizeof(IST) > 1) && k <= limit - 2 * nlanes; k += 2 * nlanes )
{
VT d0 = vx_load(src + k), d1 = vx_load(src + k + nlanes);
iMin0 = mm_sel(mm_idxmask(v_lt(d0, vMin0)), idx0, iMin0);
iMin1 = mm_sel(mm_idxmask(v_lt(d1, vMin1)), idx1, iMin1);
iMax0 = mm_sel(mm_idxmask(v_gt(d0, vMax0)), idx0, iMax0);
iMax1 = mm_sel(mm_idxmask(v_gt(d1, vMax1)), idx1, iMax1);
vMin0 = v_min(d0, vMin0); vMin1 = v_min(d1, vMin1);
vMax0 = v_max(d0, vMax0); vMax1 = v_max(d1, vMax1);
idx0 = v_add(idx0, inc2); idx1 = v_add(idx1, inc2);
}
for ( ; k < limit; k += nlanes ) // odd trailing vector
{
VT d0 = vx_load(src + k);
iMin0 = mm_sel(mm_idxmask(v_lt(d0, vMin0)), idx0, iMin0);
iMax0 = mm_sel(mm_idxmask(v_gt(d0, vMax0)), idx0, iMax0);
vMin0 = v_min(d0, vMin0); vMax0 = v_max(d0, vMax0);
idx0 = v_add(idx0, inc);
}
}
else
{
for ( ; k < limit; k += nlanes )
{
VT data = vx_load(src + k);
VT active = mm_active<VT>(mask, k, nlanes);
VT cmpMin = v_and(v_lt(data, vMin0), active);
VT cmpMax = v_and(v_gt(data, vMax0), active);
iMin0 = mm_sel(mm_idxmask(cmpMin), idx0, iMin0);
iMax0 = mm_sel(mm_idxmask(cmpMax), idx0, iMax0);
vMin0 = v_select(cmpMin, data, vMin0);
vMax0 = v_select(cmpMax, data, vMax0);
idx0 = v_add(idx0, inc);
}
}
j = k;
mm_fold_min(vMin0, iMin0, none, delta, minVal, minIdx);
mm_fold_min(vMin1, iMin1, none, delta, minVal, minIdx);
mm_fold_max(vMax0, iMax0, none, delta, maxVal, maxIdx);
mm_fold_max(vMax1, iMax1, none, delta, maxVal, maxIdx);
}
while ( j < len0 );
}
minMaxIdx_finish( src, mask, minval, maxval, minidx, maxidx, minVal, maxVal,
minIdx, maxIdx, len, startidx, j );
vx_cleanup();
}
else
{
minMaxIdx_(src, mask, minval, maxval, minidx, maxidx, len, startidx);
}
}
#endif
static void minMaxIdx_8u(const void* src_, const uchar* mask, void* minval_, void* maxval_,
size_t* minidx, size_t* maxidx, int len, size_t startidx )
{
#if (CV_SIMD || CV_SIMD_SCALABLE)
minMaxIdx_simd_<uchar, v_uint8, v_uint8, uchar, int>(
static_cast<const uchar*>(src_), mask, static_cast<int*>(minval_), static_cast<int*>(maxval_),
minidx, maxidx, len, startidx, (int)0, (int)UCHAR_MAX);
#else
minMaxIdx_(static_cast<const uchar*>(src_), mask, static_cast<int*>(minval_),
static_cast<int*>(maxval_), minidx, maxidx, len, startidx);
#endif
}
static void minMaxIdx_8s(const void* src_, const uchar* mask, void* minval_, void* maxval_,
size_t* minidx, size_t* maxidx, int len, size_t startidx )
{
#if (CV_SIMD || CV_SIMD_SCALABLE)
minMaxIdx_simd_<schar, v_int8, v_uint8, uchar, int>(
static_cast<const schar*>(src_), mask, static_cast<int*>(minval_), static_cast<int*>(maxval_),
minidx, maxidx, len, startidx, (int)SCHAR_MIN, (int)SCHAR_MAX);
#else
minMaxIdx_(static_cast<const schar*>(src_), mask, static_cast<int*>(minval_),
static_cast<int*>(maxval_), minidx, maxidx, len, startidx);
#endif
}
static void minMaxIdx_16u(const void* src_, const uchar* mask, void* minval_, void* maxval_,
size_t* minidx, size_t* maxidx, int len, size_t startidx )
{
#if (CV_SIMD || CV_SIMD_SCALABLE)
minMaxIdx_simd_<ushort, v_uint16, v_uint16, ushort, int>(
static_cast<const ushort*>(src_), mask, static_cast<int*>(minval_), static_cast<int*>(maxval_),
minidx, maxidx, len, startidx, (int)0, (int)USHRT_MAX);
#else
minMaxIdx_(static_cast<const ushort*>(src_), mask, static_cast<int*>(minval_),
static_cast<int*>(maxval_), minidx, maxidx, len, startidx);
#endif
}
static void minMaxIdx_16s(const void* src_, const uchar* mask, void* minval_, void* maxval_,
size_t* minidx, size_t* maxidx, int len, size_t startidx )
{
#if (CV_SIMD || CV_SIMD_SCALABLE)
minMaxIdx_simd_<short, v_int16, v_uint16, ushort, int>(
static_cast<const short*>(src_), mask, static_cast<int*>(minval_), static_cast<int*>(maxval_),
minidx, maxidx, len, startidx, (int)SHRT_MIN, (int)SHRT_MAX);
#else
minMaxIdx_(static_cast<const short*>(src_), mask, static_cast<int*>(minval_),
static_cast<int*>(maxval_), minidx, maxidx, len, startidx);
#endif
}
static void minMaxIdx_32s(const void* src_, const uchar* mask, void* minval_, void* maxval_,
size_t* minidx, size_t* maxidx, int len, size_t startidx )
{
#if (CV_SIMD || CV_SIMD_SCALABLE)
minMaxIdx_simd_<int, v_int32, v_uint32, uint, int>(
static_cast<const int*>(src_), mask, static_cast<int*>(minval_), static_cast<int*>(maxval_),
minidx, maxidx, len, startidx, INT_MIN, INT_MAX);
#else
minMaxIdx_(static_cast<const int*>(src_), mask, static_cast<int*>(minval_),
static_cast<int*>(maxval_), minidx, maxidx, len, startidx);
#endif
}
static void minMaxIdx_32f(const void* src_, const uchar* mask, void* minval_, void* maxval_,
size_t* minidx, size_t* maxidx, int len, size_t startidx )
{
#if (CV_SIMD || CV_SIMD_SCALABLE)
minMaxIdx_simd_<float, v_float32, v_uint32, uint, float>(
static_cast<const float*>(src_), mask, static_cast<float*>(minval_), static_cast<float*>(maxval_),
minidx, maxidx, len, startidx, FLT_MIN, FLT_MAX);
#else
minMaxIdx_(static_cast<const float*>(src_), mask, static_cast<float*>(minval_),
static_cast<float*>(maxval_), minidx, maxidx, len, startidx);
#endif
}
static void minMaxIdx_64f(const void* src_, const uchar* mask, void* minval_, void* maxval_,
size_t* minidx, size_t* maxidx, int len, size_t startidx )
{
#if (CV_SIMD_64F || CV_SIMD_SCALABLE_64F)
minMaxIdx_simd_<double, v_float64, v_uint64, uint64_t, double>(
static_cast<const double*>(src_), mask, static_cast<double*>(minval_), static_cast<double*>(maxval_),
minidx, maxidx, len, startidx, DBL_MIN, DBL_MAX);
#else
minMaxIdx_(static_cast<const double*>(src_), mask, static_cast<double*>(minval_),
static_cast<double*>(maxval_), minidx, maxidx, len, startidx);
#endif
}
MinMaxIdxFunc getMinmaxTab(int depth)
{
static MinMaxIdxFunc minmaxTab[CV_DEPTH_MAX] =
{
GET_OPTIMIZED(minMaxIdx_8u), GET_OPTIMIZED(minMaxIdx_8s),
GET_OPTIMIZED(minMaxIdx_16u), GET_OPTIMIZED(minMaxIdx_16s),
GET_OPTIMIZED(minMaxIdx_32s),
GET_OPTIMIZED(minMaxIdx_32f), GET_OPTIMIZED(minMaxIdx_64f),
0
};
return minmaxTab[depth];
}
#endif // CV_CPU_OPTIMIZATION_DECLARATIONS_ONLY
CV_CPU_OPTIMIZATION_NAMESPACE_END
} // namespace cv