LCOV - code coverage report
Current view: top level - gcore - gdal_minmax_element.hpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 341 342 99.7 %
Date: 2025-08-01 10:10:57 Functions: 374 481 77.8 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  * Project:  GDAL Core
       3             :  * Purpose:  Utility functions to find minimum and maximum values in a buffer
       4             :  * Author:   Even Rouault, <even dot rouault at spatialys.com>
       5             :  *
       6             :  ******************************************************************************
       7             :  * Copyright (c) 2024, Even Rouault <even dot rouault at spatialys.com>
       8             :  *
       9             :  * SPDX-License-Identifier: MIT
      10             :  ****************************************************************************/
      11             : 
      12             : #ifndef GDAL_MINMAX_ELEMENT_INCLUDED
      13             : #define GDAL_MINMAX_ELEMENT_INCLUDED
      14             : 
      15             : // NOTE: This header requires C++17
      16             : 
      17             : // This file may be vendored by other applications than GDAL
      18             : // WARNING: if modifying this file, please also update the upstream GDAL version
      19             : // at https://github.com/OSGeo/gdal/blob/master/gcore/gdal_minmax_element.hpp
      20             : 
      21             : #include <algorithm>
      22             : #include <cassert>
      23             : #include <cmath>
      24             : #include <cstdint>
      25             : #include <limits>
      26             : #include <type_traits>
      27             : #include <utility>
      28             : 
      29             : #include "gdal.h"
      30             : 
      31             : #if GDAL_VERSION_NUM >= GDAL_COMPUTE_VERSION(3, 11, 0)
      32             : #include "cpl_float.h"
      33             : #endif
      34             : 
      35             : #ifdef GDAL_COMPILATION
      36             : #define GDAL_MINMAXELT_NS gdal
      37             : #elif !defined(GDAL_MINMAXELT_NS)
      38             : #error "Please define the GDAL_MINMAXELT_NS macro to define the namespace"
      39             : #endif
      40             : 
      41             : #ifdef USE_NEON_OPTIMIZATIONS
      42             : #include "include_sse2neon.h"
      43             : #define GDAL_MINMAX_ELEMENT_USE_SSE2
      44             : #else
      45             : #if defined(__x86_64) || defined(_M_X64)
      46             : #define GDAL_MINMAX_ELEMENT_USE_SSE2
      47             : #endif
      48             : #ifdef GDAL_MINMAX_ELEMENT_USE_SSE2
      49             : // SSE2 header
      50             : #include <emmintrin.h>
      51             : #if defined(__SSE4_1__) || defined(__AVX__)
      52             : #include <smmintrin.h>
      53             : #endif
      54             : #endif
      55             : #endif
      56             : 
      57             : #include "gdal_priv_templates.hpp"
      58             : 
      59             : namespace GDAL_MINMAXELT_NS
      60             : {
      61             : namespace detail
      62             : {
      63             : 
      64             : template <class T> struct is_floating_point
      65             : {
      66             :     static constexpr bool value = std::is_floating_point_v<T>
      67             : #if GDAL_VERSION_NUM >= GDAL_COMPUTE_VERSION(3, 11, 0)
      68             :                                   || std::is_same_v<T, GFloat16>
      69             : #endif
      70             :         ;
      71             : };
      72             : 
      73             : template <class T>
      74             : constexpr bool is_floating_point_v = is_floating_point<T>::value;
      75             : 
      76             : /************************************************************************/
      77             : /*                            compScalar()                              */
      78             : /************************************************************************/
      79             : 
      80       16445 : template <class T, bool IS_MAX> inline static bool compScalar(T x, T y)
      81             : {
      82             :     if constexpr (IS_MAX)
      83        7954 :         return x > y;
      84             :     else
      85        8491 :         return x < y;
      86             : }
      87             : 
      88        1390 : template <typename T> inline static bool IsNan(T x)
      89             : {
      90             :     // We need to write `using std::isnan` instead of directly using
      91             :     // `std::isnan` because `std::isnan` only supports the types
      92             :     // `float` and `double`. The `isnan` for `cpl::Float16` is found in the
      93             :     // `cpl` namespace via argument-dependent lookup
      94             :     // <https://en.cppreference.com/w/cpp/language/adl>.
      95             :     using std::isnan;
      96        1390 :     return isnan(x);
      97             : }
      98             : 
      99        7005 : template <class T> inline static bool compEqual(T x, T y)
     100             : {
     101        7005 :     return x == y;
     102             : }
     103             : 
     104             : // On Intel/Neon, we do comparisons on uint16_t instead of casting to float for
     105             : // faster execution.
     106             : #if GDAL_VERSION_NUM >= GDAL_COMPUTE_VERSION(3, 11, 0) &&                      \
     107             :     defined(GDAL_MINMAX_ELEMENT_USE_SSE2)
     108        8748 : template <> bool IsNan<GFloat16>(GFloat16 x)
     109             : {
     110             :     uint16_t iX;
     111        8748 :     memcpy(&iX, &x, sizeof(x));
     112             :     // Check that the 5 bits of the exponent are set and the mantissa is not zero.
     113        8748 :     return (iX & 0x7fff) > 0x7c00;
     114             : }
     115             : 
     116        1442 : template <> bool compEqual<GFloat16>(GFloat16 x, GFloat16 y)
     117             : {
     118             :     uint16_t iX, iY;
     119        1442 :     memcpy(&iX, &x, sizeof(x));
     120        1442 :     memcpy(&iY, &y, sizeof(y));
     121             :     // Given our usage where y cannot be NaN we can skip the IsNan tests
     122        1442 :     assert(!IsNan(y));
     123        2876 :     return (iX == iY ||
     124             :             // Also check +0 == -0
     125        2876 :             ((iX | iY) == (1 << 15)));
     126             : }
     127             : 
     128        3640 : template <> bool compScalar<GFloat16, true>(GFloat16 x, GFloat16 y)
     129             : {
     130             :     uint16_t iX, iY;
     131        3640 :     memcpy(&iX, &x, sizeof(x));
     132        3640 :     memcpy(&iY, &y, sizeof(y));
     133             :     bool ret;
     134        3640 :     if (IsNan(x) || IsNan(y))
     135             :     {
     136         805 :         ret = false;
     137             :     }
     138        2835 :     else if (!(iX >> 15))
     139             :     {
     140             :         // +0 considered > -0. We don't really care
     141         163 :         ret = (iY >> 15) || iX > iY;
     142             :     }
     143             :     else
     144             :     {
     145        2672 :         ret = (iY >> 15) && iX < iY;
     146             :     }
     147        3640 :     return ret;
     148             : }
     149             : 
     150        1484 : template <> bool compScalar<GFloat16, false>(GFloat16 x, GFloat16 y)
     151             : {
     152        1484 :     return compScalar<GFloat16, true>(y, x);
     153             : }
     154             : #endif
     155             : 
     156             : /************************************************************************/
     157             : /*                 extremum_element_with_nan_generic()                  */
     158             : /************************************************************************/
     159             : 
     160             : template <class T, bool IS_MAX>
     161             : inline size_t extremum_element_with_nan_generic(const T *v, size_t size)
     162             : {
     163             :     if (size == 0)
     164             :         return 0;
     165             :     size_t idx_of_extremum = 0;
     166             :     auto extremum = v[0];
     167             :     bool extremum_is_nan = IsNan(extremum);
     168             :     size_t i = 1;
     169             :     for (; i < size; ++i)
     170             :     {
     171             :         if (compScalar<T, IS_MAX>(v[i], extremum) ||
     172             :             (extremum_is_nan && !IsNan(v[i])))
     173             :         {
     174             :             extremum = v[i];
     175             :             idx_of_extremum = i;
     176             :             extremum_is_nan = false;
     177             :         }
     178             :     }
     179             :     return idx_of_extremum;
     180             : }
     181             : 
     182             : /************************************************************************/
     183             : /*                 extremum_element_with_nan_generic()                  */
     184             : /************************************************************************/
     185             : 
     186             : template <class T, bool IS_MAX>
     187             : inline size_t extremum_element_with_nan_generic(const T *v, size_t size,
     188             :                                                 T noDataValue)
     189             : {
     190             :     if (IsNan(noDataValue))
     191             :         return extremum_element_with_nan_generic<T, IS_MAX>(v, size);
     192             :     if (size == 0)
     193             :         return 0;
     194             :     size_t idx_of_extremum = 0;
     195             :     auto extremum = v[0];
     196             :     bool extremum_is_nan_or_nodata =
     197             :         IsNan(extremum) || compEqual(extremum, noDataValue);
     198             :     size_t i = 1;
     199             :     for (; i < size; ++i)
     200             :     {
     201             :         if (!compEqual(v[i], noDataValue) &&
     202             :             (compScalar<T, IS_MAX>(v[i], extremum) ||
     203             :              (extremum_is_nan_or_nodata && !IsNan(v[i]))))
     204             :         {
     205             :             extremum = v[i];
     206             :             idx_of_extremum = i;
     207             :             extremum_is_nan_or_nodata = false;
     208             :         }
     209             :     }
     210             :     return idx_of_extremum;
     211             : }
     212             : 
     213             : /************************************************************************/
     214             : /*                       extremum_element_generic()                     */
     215             : /************************************************************************/
     216             : 
     217             : template <class T, bool IS_MAX>
     218             : inline size_t extremum_element_generic(const T *buffer, size_t size,
     219             :                                        bool bHasNoData, T noDataValue)
     220             : {
     221             :     if (bHasNoData)
     222             :     {
     223             :         if constexpr (is_floating_point_v<T>)
     224             :         {
     225             :             if (IsNan(noDataValue))
     226             :             {
     227             :                 if constexpr (IS_MAX)
     228             :                 {
     229             :                     return std::max_element(buffer, buffer + size,
     230             :                                             [](T a, T b) {
     231             :                                                 return IsNan(b)   ? false
     232             :                                                        : IsNan(a) ? true
     233             :                                                                   : a < b;
     234             :                                             }) -
     235             :                            buffer;
     236             :                 }
     237             :                 else
     238             :                 {
     239             :                     return std::min_element(buffer, buffer + size,
     240             :                                             [](T a, T b) {
     241             :                                                 return IsNan(b)   ? true
     242             :                                                        : IsNan(a) ? false
     243             :                                                                   : a < b;
     244             :                                             }) -
     245             :                            buffer;
     246             :                 }
     247             :             }
     248             :             else
     249             :             {
     250             :                 if constexpr (IS_MAX)
     251             :                 {
     252             :                     return std::max_element(buffer, buffer + size,
     253             :                                             [noDataValue](T a, T b)
     254             :                                             {
     255             :                                                 return IsNan(b)   ? false
     256             :                                                        : IsNan(a) ? true
     257             :                                                        : (b == noDataValue)
     258             :                                                            ? false
     259             :                                                        : (a == noDataValue)
     260             :                                                            ? true
     261             :                                                            : a < b;
     262             :                                             }) -
     263             :                            buffer;
     264             :                 }
     265             :                 else
     266             :                 {
     267             :                     return std::min_element(buffer, buffer + size,
     268             :                                             [noDataValue](T a, T b)
     269             :                                             {
     270             :                                                 return IsNan(b)   ? true
     271             :                                                        : IsNan(a) ? false
     272             :                                                        : (b == noDataValue)
     273             :                                                            ? true
     274             :                                                        : (a == noDataValue)
     275             :                                                            ? false
     276             :                                                            : a < b;
     277             :                                             }) -
     278             :                            buffer;
     279             :                 }
     280             :             }
     281             :         }
     282             :         else
     283             :         {
     284             :             if constexpr (IS_MAX)
     285             :             {
     286             :                 return std::max_element(buffer, buffer + size,
     287             :                                         [noDataValue](T a, T b) {
     288             :                                             return (b == noDataValue)   ? false
     289             :                                                    : (a == noDataValue) ? true
     290             :                                                                         : a < b;
     291             :                                         }) -
     292             :                        buffer;
     293             :             }
     294             :             else
     295             :             {
     296             :                 return std::min_element(buffer, buffer + size,
     297             :                                         [noDataValue](T a, T b) {
     298             :                                             return (b == noDataValue)   ? true
     299             :                                                    : (a == noDataValue) ? false
     300             :                                                                         : a < b;
     301             :                                         }) -
     302             :                        buffer;
     303             :             }
     304             :         }
     305             :     }
     306             :     else
     307             :     {
     308             :         if constexpr (is_floating_point_v<T>)
     309             :         {
     310             :             if constexpr (IS_MAX)
     311             :             {
     312             :                 return std::max_element(buffer, buffer + size,
     313             :                                         [](T a, T b) {
     314             :                                             return IsNan(b)   ? false
     315             :                                                    : IsNan(a) ? true
     316             :                                                               : a < b;
     317             :                                         }) -
     318             :                        buffer;
     319             :             }
     320             :             else
     321             :             {
     322             :                 return std::min_element(buffer, buffer + size,
     323             :                                         [](T a, T b) {
     324             :                                             return IsNan(b)   ? true
     325             :                                                    : IsNan(a) ? false
     326             :                                                               : a < b;
     327             :                                         }) -
     328             :                        buffer;
     329             :             }
     330             :         }
     331             :         else
     332             :         {
     333             :             if constexpr (IS_MAX)
     334             :             {
     335             :                 return std::max_element(buffer, buffer + size) - buffer;
     336             :             }
     337             :             else
     338             :             {
     339             :                 return std::min_element(buffer, buffer + size) - buffer;
     340             :             }
     341             :         }
     342             :     }
     343             : }
     344             : 
     345             : #ifdef GDAL_MINMAX_ELEMENT_USE_SSE2
     346             : 
     347             : /************************************************************************/
     348             : /*                     extremum_element_sse2()                          */
     349             : /************************************************************************/
     350             : 
     351         107 : static inline int8_t Shift8(uint8_t x)
     352             : {
     353         107 :     return static_cast<int8_t>(x + std::numeric_limits<int8_t>::min());
     354             : }
     355             : 
     356          48 : static inline int16_t Shift16(uint16_t x)
     357             : {
     358          48 :     return static_cast<int16_t>(x + std::numeric_limits<int16_t>::min());
     359             : }
     360             : 
     361             : CPL_NOSANITIZE_UNSIGNED_INT_OVERFLOW
     362          46 : static inline int32_t Shift32(uint32_t x)
     363             : {
     364          46 :     x += static_cast<uint32_t>(std::numeric_limits<int32_t>::min());
     365             :     int32_t ret;
     366          46 :     memcpy(&ret, &x, sizeof(x));
     367          46 :     return ret;
     368             : }
     369             : 
     370             : CPL_NOSANITIZE_UNSIGNED_INT_OVERFLOW
     371          45 : static inline int64_t Shift64(uint64_t x)
     372             : {
     373          45 :     x += static_cast<uint64_t>(std::numeric_limits<int64_t>::min());
     374             :     int64_t ret;
     375          45 :     memcpy(&ret, &x, sizeof(x));
     376          45 :     return ret;
     377             : }
     378             : 
     379             : // Return a _mm128[i|d] register with all its elements set to x
     380         847 : template <class T> static inline auto set1(T x)
     381             : {
     382             :     if constexpr (std::is_same_v<T, uint8_t>)
     383         214 :         return _mm_set1_epi8(Shift8(x));
     384             :     else if constexpr (std::is_same_v<T, int8_t>)
     385          42 :         return _mm_set1_epi8(x);
     386             :     else if constexpr (std::is_same_v<T, uint16_t>)
     387          96 :         return _mm_set1_epi16(Shift16(x));
     388             :     else if constexpr (std::is_same_v<T, int16_t>)
     389         134 :         return _mm_set1_epi16(x);
     390             :     else if constexpr (std::is_same_v<T, uint32_t>)
     391          92 :         return _mm_set1_epi32(Shift32(x));
     392             :     else if constexpr (std::is_same_v<T, int32_t>)
     393          58 :         return _mm_set1_epi32(x);
     394             :     else if constexpr (std::is_same_v<T, uint64_t>)
     395          90 :         return _mm_set1_epi64x(Shift64(x));
     396             :     else if constexpr (std::is_same_v<T, int64_t>)
     397          68 :         return _mm_set1_epi64x(x);
     398             : #if GDAL_VERSION_NUM >= GDAL_COMPUTE_VERSION(3, 11, 0)
     399             :     else if constexpr (std::is_same_v<T, GFloat16>)
     400             :     {
     401             :         int16_t iX;
     402         107 :         memcpy(&iX, &x, sizeof(x));
     403         214 :         return _mm_set1_epi16(iX);
     404             :     }
     405             : #endif
     406             :     else if constexpr (std::is_same_v<T, float>)
     407         154 :         return _mm_set1_ps(x);
     408             :     else
     409         126 :         return _mm_set1_pd(x);
     410             : }
     411             : 
     412             : // Return a _mm128[i|d] register with all its elements set to x
     413         922 : template <class T> static inline auto set1_unshifted(T x)
     414             : {
     415             :     if constexpr (std::is_same_v<T, uint8_t>)
     416             :     {
     417             :         int8_t xSigned;
     418         121 :         memcpy(&xSigned, &x, sizeof(xSigned));
     419         242 :         return _mm_set1_epi8(xSigned);
     420             :     }
     421             :     else if constexpr (std::is_same_v<T, int8_t>)
     422          84 :         return _mm_set1_epi8(x);
     423             :     else if constexpr (std::is_same_v<T, uint16_t>)
     424             :     {
     425             :         int16_t xSigned;
     426          57 :         memcpy(&xSigned, &x, sizeof(xSigned));
     427         114 :         return _mm_set1_epi16(xSigned);
     428             :     }
     429             :     else if constexpr (std::is_same_v<T, int16_t>)
     430         130 :         return _mm_set1_epi16(x);
     431             :     else if constexpr (std::is_same_v<T, uint32_t>)
     432             :     {
     433             :         int32_t xSigned;
     434          54 :         memcpy(&xSigned, &x, sizeof(xSigned));
     435         108 :         return _mm_set1_epi32(xSigned);
     436             :     }
     437             :     else if constexpr (std::is_same_v<T, int32_t>)
     438          60 :         return _mm_set1_epi32(x);
     439             :     else if constexpr (std::is_same_v<T, uint64_t>)
     440             :     {
     441             :         int64_t xSigned;
     442          53 :         memcpy(&xSigned, &x, sizeof(xSigned));
     443         106 :         return _mm_set1_epi64x(xSigned);
     444             :     }
     445             :     else if constexpr (std::is_same_v<T, int64_t>)
     446          65 :         return _mm_set1_epi64x(x);
     447             : #if GDAL_VERSION_NUM >= GDAL_COMPUTE_VERSION(3, 11, 0)
     448             :     else if constexpr (std::is_same_v<T, GFloat16>)
     449             :     {
     450             :         int16_t iX;
     451         128 :         memcpy(&iX, &x, sizeof(x));
     452         256 :         return _mm_set1_epi16(iX);
     453             :     }
     454             : #endif
     455             :     else if constexpr (std::is_same_v<T, float>)
     456         149 :         return _mm_set1_ps(x);
     457             :     else
     458         128 :         return _mm_set1_pd(x);
     459             : }
     460             : 
     461             : // Load as many values of type T at a _mm128[i|d] register can contain from x
     462   145001866 : template <class T> static inline auto loadv(const T *x)
     463             : {
     464             :     if constexpr (std::is_same_v<T, float>)
     465    20000132 :         return _mm_loadu_ps(x);
     466             :     else if constexpr (std::is_same_v<T, double>)
     467    40000148 :         return _mm_loadu_pd(x);
     468             :     else
     469    85001586 :         return _mm_loadu_si128(reinterpret_cast<const __m128i *>(x));
     470             : }
     471             : 
     472    15000144 : inline __m128i IsNanGFloat16(__m128i x)
     473             : {
     474             :     // (iX & 0x7fff) > 0x7c00
     475    45000332 :     return _mm_cmpgt_epi16(_mm_and_si128(x, _mm_set1_epi16(0x7fff)),
     476    15000144 :                            _mm_set1_epi16(0x7c00));
     477             : }
     478             : 
     479             : template <class T, class SSE_T>
     480    82500682 : static inline SSE_T blendv(SSE_T a, SSE_T b, SSE_T mask)
     481             : {
     482             :     if constexpr (std::is_same_v<T, float>)
     483             :     {
     484             : #if defined(__SSE4_1__) || defined(__AVX__) || defined(USE_NEON_OPTIMIZATIONS)
     485             :         return _mm_blendv_ps(a, b, mask);
     486             : #else
     487    30000180 :         return _mm_or_ps(_mm_andnot_ps(mask, a), _mm_and_ps(mask, b));
     488             : #endif
     489             :     }
     490             :     else if constexpr (std::is_same_v<T, double>)
     491             :     {
     492             : #if defined(__SSE4_1__) || defined(__AVX__) || defined(USE_NEON_OPTIMIZATIONS)
     493             :         return _mm_blendv_pd(a, b, mask);
     494             : #else
     495    60000372 :         return _mm_or_pd(_mm_andnot_pd(mask, a), _mm_and_pd(mask, b));
     496             : #endif
     497             :     }
     498             :     else
     499             :     {
     500             : #if defined(__SSE4_1__) || defined(__AVX__) || defined(USE_NEON_OPTIMIZATIONS)
     501             :         return _mm_blendv_epi8(a, b, mask);
     502             : #else
     503   157501664 :         return _mm_or_si128(_mm_andnot_si128(mask, a), _mm_and_si128(mask, b));
     504             : #endif
     505             :     }
     506             : }
     507             : 
     508    10000058 : inline __m128i cmpgt_ph(__m128i x, __m128i y)
     509             : {
     510             : #ifdef slow
     511             :     GFloat16 vx[8], vy[8];
     512             :     int16_t res[8];
     513             :     _mm_storeu_si128(reinterpret_cast<__m128i *>(vx), x);
     514             :     _mm_storeu_si128(reinterpret_cast<__m128i *>(vy), y);
     515             :     for (int i = 0; i < 8; ++i)
     516             :     {
     517             :         res[i] = vx[i] > vy[i] ? -1 : 0;
     518             :     }
     519             :     return _mm_loadu_si128(reinterpret_cast<const __m128i *>(res));
     520             : #else
     521    10000058 :     const auto x_is_negative = _mm_srai_epi16(x, 15);
     522    10000058 :     const auto y_is_negative = _mm_srai_epi16(y, 15);
     523    40000252 :     return _mm_andnot_si128(
     524             :         // only x can be NaN given how we use this method
     525             :         IsNanGFloat16(x),
     526             :         blendv<int16_t>(_mm_or_si128(y_is_negative, _mm_cmpgt_epi16(x, y)),
     527             :                         _mm_and_si128(y_is_negative, _mm_cmpgt_epi16(y, x)),
     528    10000058 :                         x_is_negative));
     529             : #endif
     530             : }
     531             : 
     532    40000744 : inline __m128i cmpgt_epi64(__m128i x, __m128i y)
     533             : {
     534             : #if defined(__SSE4_2__) || defined(__AVX__) || defined(USE_NEON_OPTIMIZATIONS)
     535             :     return _mm_cmpgt_epi64(x, y);
     536             : #else
     537   120002232 :     auto tmp = _mm_and_si128(_mm_sub_epi64(y, x), _mm_cmpeq_epi32(x, y));
     538    40000744 :     tmp = _mm_or_si128(tmp, _mm_cmpgt_epi32(x, y));
     539             :     // Replicate the 2 odd-indexed (hi) 32-bit word into the 2 even-indexed (lo)
     540             :     // ones
     541    40000744 :     return _mm_shuffle_epi32(tmp, _MM_SHUFFLE(3, 3, 1, 1));
     542             : #endif
     543             : }
     544             : 
     545             : // Return a __m128i register with bits set when x[i] < y[i] when !IS_MAX
     546             : // or x[i] > y[i] when IS_MAX
     547             : template <class T, bool IS_MAX, class SSE_T>
     548   145001776 : static inline __m128i comp(SSE_T x, SSE_T y)
     549             : {
     550             :     if constexpr (IS_MAX)
     551             :     {
     552             :         if constexpr (std::is_same_v<T, uint8_t>)
     553     3750472 :             return _mm_cmpgt_epi8(
     554             :                 _mm_add_epi8(x,
     555     1250154 :                              _mm_set1_epi8(std::numeric_limits<int8_t>::min())),
     556     1250154 :                 y);
     557             :         else if constexpr (std::is_same_v<T, int8_t>)
     558     1250002 :             return _mm_cmpgt_epi8(x, y);
     559             :         else if constexpr (std::is_same_v<T, uint16_t>)
     560     7500064 :             return _mm_cmpgt_epi16(
     561             :                 _mm_add_epi16(
     562     2500018 :                     x, _mm_set1_epi16(std::numeric_limits<int16_t>::min())),
     563     2500018 :                 y);
     564             :         else if constexpr (std::is_same_v<T, int16_t>)
     565     2500018 :             return _mm_cmpgt_epi16(x, y);
     566             :         else if constexpr (std::is_same_v<T, uint32_t>)
     567    15000180 :             return _mm_cmpgt_epi32(
     568             :                 _mm_add_epi32(
     569             :                     x, _mm_set1_epi32(std::numeric_limits<int32_t>::min())),
     570     5000050 :                 y);
     571             :         else if constexpr (std::is_same_v<T, int32_t>)
     572     5000050 :             return _mm_cmpgt_epi32(x, y);
     573             :         else if constexpr (std::is_same_v<T, uint64_t>)
     574             :         {
     575    20000248 :             return cmpgt_epi64(
     576             :                 _mm_add_epi64(
     577    10000114 :                     x, _mm_set1_epi64x(std::numeric_limits<int64_t>::min())),
     578    10000114 :                 y);
     579             :         }
     580             :         else if constexpr (std::is_same_v<T, int64_t>)
     581             :         {
     582    10000114 :             return cmpgt_epi64(x, y);
     583             :         }
     584             : #if GDAL_VERSION_NUM >= GDAL_COMPUTE_VERSION(3, 11, 0)
     585             :         else if constexpr (std::is_same_v<T, GFloat16>)
     586             :         {
     587     5000024 :             return cmpgt_ph(x, y);
     588             :         }
     589             : #endif
     590             :         else if constexpr (std::is_same_v<T, float>)
     591    20000072 :             return _mm_castps_si128(_mm_cmpgt_ps(x, y));
     592             :         else
     593    40000024 :             return _mm_castpd_si128(_mm_cmpgt_pd(x, y));
     594             :     }
     595             :     else
     596             :     {
     597             :         if constexpr (std::is_same_v<T, uint8_t>)
     598     3750508 :             return _mm_cmplt_epi8(
     599             :                 _mm_add_epi8(x,
     600     1250166 :                              _mm_set1_epi8(std::numeric_limits<int8_t>::min())),
     601     1250166 :                 y);
     602             :         else if constexpr (std::is_same_v<T, int8_t>)
     603     1250014 :             return _mm_cmplt_epi8(x, y);
     604             :         else if constexpr (std::is_same_v<T, uint16_t>)
     605     7500148 :             return _mm_cmplt_epi16(
     606             :                 _mm_add_epi16(
     607     2500046 :                     x, _mm_set1_epi16(std::numeric_limits<int16_t>::min())),
     608     2500046 :                 y);
     609             :         else if constexpr (std::is_same_v<T, int16_t>)
     610     2500046 :             return _mm_cmplt_epi16(x, y);
     611             :         else if constexpr (std::is_same_v<T, uint32_t>)
     612    15000360 :             return _mm_cmplt_epi32(
     613             :                 _mm_add_epi32(
     614             :                     x, _mm_set1_epi32(std::numeric_limits<int32_t>::min())),
     615     5000110 :                 y);
     616             :         else if constexpr (std::is_same_v<T, int32_t>)
     617     5000110 :             return _mm_cmplt_epi32(x, y);
     618             :         else if constexpr (std::is_same_v<T, uint64_t>)
     619             :         {
     620    20000496 :             return cmpgt_epi64(
     621             :                 y, _mm_add_epi64(x, _mm_set1_epi64x(
     622    20000496 :                                         std::numeric_limits<int64_t>::min())));
     623             :         }
     624             :         else if constexpr (std::is_same_v<T, int64_t>)
     625             :         {
     626    10000238 :             return cmpgt_epi64(y, x);
     627             :         }
     628             : #if GDAL_VERSION_NUM >= GDAL_COMPUTE_VERSION(3, 11, 0)
     629             :         else if constexpr (std::is_same_v<T, GFloat16>)
     630             :         {
     631     5000024 :             return cmpgt_ph(y, x);
     632             :         }
     633             : #endif
     634             :         else if constexpr (std::is_same_v<T, float>)
     635    20000192 :             return _mm_castps_si128(_mm_cmplt_ps(x, y));
     636             :         else
     637    40000272 :             return _mm_castpd_si128(_mm_cmplt_pd(x, y));
     638             :     }
     639             : }
     640             : 
     641             : template <class T, class SSE_T> static inline SSE_T compeq(SSE_T a, SSE_T b);
     642             : 
     643     1250018 : template <> __m128i compeq<uint8_t>(__m128i a, __m128i b)
     644             : {
     645     1250018 :     return _mm_cmpeq_epi8(a, b);
     646             : }
     647             : 
     648     1250002 : template <> __m128i compeq<int8_t>(__m128i a, __m128i b)
     649             : {
     650     1250002 :     return _mm_cmpeq_epi8(a, b);
     651             : }
     652             : 
     653     2500018 : template <> __m128i compeq<uint16_t>(__m128i a, __m128i b)
     654             : {
     655     2500018 :     return _mm_cmpeq_epi16(a, b);
     656             : }
     657             : 
     658     2500018 : template <> __m128i compeq<int16_t>(__m128i a, __m128i b)
     659             : {
     660     2500018 :     return _mm_cmpeq_epi16(a, b);
     661             : }
     662             : 
     663     5000050 : template <> __m128i compeq<uint32_t>(__m128i a, __m128i b)
     664             : {
     665     5000050 :     return _mm_cmpeq_epi32(a, b);
     666             : }
     667             : 
     668     5000050 : template <> __m128i compeq<int32_t>(__m128i a, __m128i b)
     669             : {
     670     5000050 :     return _mm_cmpeq_epi32(a, b);
     671             : }
     672             : 
     673    20000248 : template <> __m128i compeq<uint64_t>(__m128i a, __m128i b)
     674             : {
     675             : #if defined(__SSE4_1__) || defined(__AVX__) || defined(USE_NEON_OPTIMIZATIONS)
     676             :     return _mm_cmpeq_epi64(a, b);
     677             : #else
     678    20000248 :     auto tmp = _mm_cmpeq_epi32(a, b);
     679             :     // The shuffle swaps hi-lo 32-bit words
     680    40000496 :     return _mm_and_si128(tmp, _mm_shuffle_epi32(tmp, _MM_SHUFFLE(2, 3, 0, 1)));
     681             : #endif
     682             : }
     683             : 
     684    10000114 : template <> __m128i compeq<int64_t>(__m128i a, __m128i b)
     685             : {
     686    10000114 :     return compeq<uint64_t>(a, b);
     687             : }
     688             : 
     689    10000040 : template <> __m128 compeq<float>(__m128 a, __m128 b)
     690             : {
     691    10000040 :     return _mm_cmpeq_ps(a, b);
     692             : }
     693             : 
     694    20000124 : template <> __m128d compeq<double>(__m128d a, __m128d b)
     695             : {
     696    20000124 :     return _mm_cmpeq_pd(a, b);
     697             : }
     698             : 
     699             : #if GDAL_VERSION_NUM >= GDAL_COMPUTE_VERSION(3, 11, 0)
     700     5000036 : template <> __m128i compeq<GFloat16>(__m128i a, __m128i b)
     701             : {
     702             :     // !isnan(a) && !isnan(b) && (a == b || (a|b) == 0x8000)
     703    30000236 :     return _mm_andnot_si128(
     704             :         IsNanGFloat16(a),  // b cannot be NaN given how we use this method
     705             :         _mm_or_si128(_mm_cmpeq_epi16(a, b),
     706             :                      _mm_cmpeq_epi16(
     707             :                          _mm_or_si128(a, b),
     708    10000082 :                          _mm_set1_epi16(std::numeric_limits<int16_t>::min()))));
     709             : }
     710             : #endif
     711             : 
     712             : // Using SSE2
     713             : template <class T, bool IS_MAX, bool HAS_NODATA>
     714             : #if defined(__GNUC__)
     715             : __attribute__((noinline))
     716             : #endif
     717             : size_t
     718         260 : extremum_element_sse2(const T *v, size_t size, T noDataValue)
     719             : {
     720             :     static_assert(std::is_same_v<T, uint8_t> || std::is_same_v<T, int8_t> ||
     721             :                   std::is_same_v<T, uint16_t> || std::is_same_v<T, int16_t> ||
     722             :                   std::is_same_v<T, uint32_t> || std::is_same_v<T, int32_t> ||
     723             :                   std::is_same_v<T, uint64_t> || std::is_same_v<T, int64_t> ||
     724             :                   is_floating_point_v<T>);
     725         260 :     if (size == 0)
     726          22 :         return 0;
     727         238 :     size_t idx_of_extremum = 0;
     728         238 :     T extremum = v[0];
     729         238 :     [[maybe_unused]] bool extremum_is_invalid = false;
     730             :     if constexpr (is_floating_point_v<T>)
     731             :     {
     732             :         if constexpr (HAS_NODATA)
     733             :         {
     734          44 :             if (IsNan(noDataValue))
     735             :             {
     736           6 :                 return extremum_element_sse2<T, IS_MAX, false>(
     737           4 :                     v, size, static_cast<T>(0));
     738             :             }
     739             :         }
     740          86 :         extremum_is_invalid = IsNan(extremum);
     741             :     }
     742             :     if constexpr (HAS_NODATA)
     743             :     {
     744         115 :         if (compEqual(extremum, noDataValue))
     745          25 :             extremum_is_invalid = true;
     746             :     }
     747         234 :     size_t i = 1;
     748             : 
     749         234 :     constexpr size_t VALS_PER_REG = sizeof(set1(extremum)) / sizeof(extremum);
     750         234 :     constexpr int LOOP_UNROLLING = 4;
     751             :     // If changing the value, then we need to adjust the number of sse_valX
     752             :     // loading in the loop.
     753             :     static_assert(LOOP_UNROLLING == 4);
     754         234 :     constexpr size_t VALS_PER_ITER = VALS_PER_REG * LOOP_UNROLLING;
     755             : 
     756       71940 :     const auto update = [v, noDataValue, &extremum, &idx_of_extremum,
     757             :                          &extremum_is_invalid](size_t idx)
     758             :     {
     759             :         if constexpr (HAS_NODATA)
     760             :         {
     761        8332 :             if (compEqual(v[idx], noDataValue))
     762         689 :                 return;
     763        7643 :             if (extremum_is_invalid)
     764             :             {
     765             :                 if constexpr (is_floating_point_v<T>)
     766             :                 {
     767          24 :                     if (IsNan(v[idx]))
     768           6 :                         return;
     769             :                 }
     770          36 :                 extremum = v[idx];
     771          36 :                 idx_of_extremum = idx;
     772          36 :                 extremum_is_invalid = false;
     773          36 :                 return;
     774             :             }
     775             :         }
     776             :         else
     777             :         {
     778       12484 :             CPL_IGNORE_RET_VAL(noDataValue);
     779             :         }
     780       20085 :         if (compScalar<T, IS_MAX>(v[idx], extremum))
     781             :         {
     782         926 :             extremum = v[idx];
     783         926 :             idx_of_extremum = idx;
     784         926 :             extremum_is_invalid = false;
     785             :         }
     786             :         else if constexpr (is_floating_point_v<T>)
     787             :         {
     788        7606 :             if (extremum_is_invalid && !IsNan(v[idx]))
     789             :             {
     790          28 :                 extremum = v[idx];
     791          28 :                 idx_of_extremum = idx;
     792          28 :                 extremum_is_invalid = false;
     793             :             }
     794             :         }
     795             :     };
     796             : 
     797        4381 :     for (; i < VALS_PER_ITER && i < size; ++i)
     798             :     {
     799        4147 :         update(i);
     800             :     }
     801             : 
     802         234 :     [[maybe_unused]] auto sse_neutral = set1_unshifted(static_cast<T>(0));
     803         234 :     [[maybe_unused]] auto sse_nodata = set1_unshifted(noDataValue);
     804             :     if constexpr (HAS_NODATA || is_floating_point_v<T>)
     805             :     {
     806        2240 :         for (; i < size && extremum_is_invalid; ++i)
     807             :         {
     808        2079 :             update(i);
     809             :         }
     810         161 :         if (!extremum_is_invalid)
     811             :         {
     812         234 :             for (; i < size && (i % VALS_PER_ITER) != 0; ++i)
     813             :             {
     814          76 :                 update(i);
     815             :             }
     816         158 :             sse_neutral = set1_unshifted(extremum);
     817             :         }
     818             :     }
     819             : 
     820         234 :     auto sse_extremum = set1(extremum);
     821             : 
     822         234 :     [[maybe_unused]] size_t hits = 0;
     823         234 :     const auto sse_iter_count = (size / VALS_PER_ITER) * VALS_PER_ITER;
     824    36250682 :     for (; i < sse_iter_count; i += VALS_PER_ITER)
     825             :     {
     826             :         // A bit of loop unrolling to save 3/4 of slow movemask operations.
     827    36250472 :         auto sse_val0 = loadv(v + i + 0 * VALS_PER_REG);
     828    36250472 :         auto sse_val1 = loadv(v + i + 1 * VALS_PER_REG);
     829    36250472 :         auto sse_val2 = loadv(v + i + 2 * VALS_PER_REG);
     830    36250472 :         auto sse_val3 = loadv(v + i + 3 * VALS_PER_REG);
     831             : 
     832             :         if constexpr (HAS_NODATA)
     833             :         {
     834             :             // Replace all components that are at the nodata value by a
     835             :             // neutral value (current minimum)
     836    18125160 :             const auto replaceNoDataByNeutral =
     837   145001168 :                 [sse_neutral, sse_nodata](auto sse_val)
     838             :             {
     839    72500604 :                 const auto eq_nodata = compeq<T>(sse_val, sse_nodata);
     840    72500604 :                 return blendv<T>(sse_val, sse_neutral, eq_nodata);
     841             :             };
     842             : 
     843    18125160 :             sse_val0 = replaceNoDataByNeutral(sse_val0);
     844    18125160 :             sse_val1 = replaceNoDataByNeutral(sse_val1);
     845    18125160 :             sse_val2 = replaceNoDataByNeutral(sse_val2);
     846    18125160 :             sse_val3 = replaceNoDataByNeutral(sse_val3);
     847             :         }
     848             : 
     849   145001864 :         if (_mm_movemask_epi8(_mm_or_si128(
     850             :                 _mm_or_si128(comp<T, IS_MAX>(sse_val0, sse_extremum),
     851             :                              comp<T, IS_MAX>(sse_val1, sse_extremum)),
     852             :                 _mm_or_si128(comp<T, IS_MAX>(sse_val2, sse_extremum),
     853    36250472 :                              comp<T, IS_MAX>(sse_val3, sse_extremum)))) != 0)
     854             :         {
     855             :             if constexpr (!std::is_same_v<T, int8_t> &&
     856             :                           !std::is_same_v<T, uint8_t>)
     857             :             {
     858             :                 // The above tests excluding int8_t/uint8_t is due to the fact
     859             :                 // with those small ranges of values we will quickly converge
     860             :                 // to the minimum, so no need to do the below "smart" test.
     861             : 
     862         543 :                 if (++hits == size / 16)
     863             :                 {
     864             :                     // If we have an almost sorted array, then using this code path
     865             :                     // will hurt performance. Arbitrary give up if we get here
     866             :                     // more than 1. / 16 of the size of the array.
     867             :                     // fprintf(stderr, "going to non-vector path\n");
     868           0 :                     break;
     869             :                 }
     870             :             }
     871       14917 :             for (size_t j = 0; j < VALS_PER_ITER; j++)
     872             :             {
     873       14304 :                 update(i + j);
     874             :             }
     875             : 
     876         613 :             sse_extremum = set1(extremum);
     877             :             if constexpr (HAS_NODATA)
     878             :             {
     879         296 :                 sse_neutral = set1_unshifted(extremum);
     880             :             }
     881             :         }
     882             :     }
     883         444 :     for (; i < size; ++i)
     884             :     {
     885         210 :         update(i);
     886             :     }
     887         234 :     return idx_of_extremum;
     888             : }
     889             : 
     890             : /************************************************************************/
     891             : /*                         extremum_element()                           */
     892             : /************************************************************************/
     893             : 
     894             : template <class T, bool IS_MAX>
     895         130 : inline size_t extremum_element(const T *buffer, size_t size, T noDataValue)
     896             : {
     897         130 :     return extremum_element_sse2<T, IS_MAX, true>(buffer, size, noDataValue);
     898             : }
     899             : 
     900             : template <class T, bool IS_MAX>
     901         126 : inline size_t extremum_element(const T *buffer, size_t size)
     902             : {
     903         141 :     return extremum_element_sse2<T, IS_MAX, false>(buffer, size,
     904         141 :                                                    static_cast<T>(0));
     905             : }
     906             : 
     907             : #else
     908             : 
     909             : template <class T, bool IS_MAX>
     910             : inline size_t extremum_element(const T *buffer, size_t size, T noDataValue)
     911             : {
     912             :     if constexpr (is_floating_point_v<T>)
     913             :         return extremum_element_with_nan_generic<T, IS_MAX>(buffer, size,
     914             :                                                             noDataValue);
     915             :     else
     916             :         return extremum_element_generic<T, IS_MAX>(buffer, size, true,
     917             :                                                    noDataValue);
     918             : }
     919             : 
     920             : template <class T, bool IS_MAX>
     921             : inline size_t extremum_element(const T *buffer, size_t size)
     922             : {
     923             :     if constexpr (is_floating_point_v<T>)
     924             :         return extremum_element_with_nan_generic<T, IS_MAX>(buffer, size);
     925             :     else
     926             :         return extremum_element_generic<T, IS_MAX>(buffer, size, false,
     927             :                                                    static_cast<T>(0));
     928             : }
     929             : 
     930             : #endif
     931             : 
     932             : /************************************************************************/
     933             : /*                            extremum_element()                        */
     934             : /************************************************************************/
     935             : 
     936             : template <class T, bool IS_MAX>
     937         256 : inline size_t extremum_element(const T *buffer, size_t size, bool bHasNoData,
     938             :                                T noDataValue)
     939             : {
     940             : #if GDAL_VERSION_NUM >= GDAL_COMPUTE_VERSION(3, 11, 0) &&                      \
     941             :     !defined(GDAL_MINMAX_ELEMENT_USE_SSE2)
     942             :     if constexpr (std::is_same_v<T, GFloat16>)
     943             :     {
     944             :         if (bHasNoData)
     945             :             return extremum_element_with_nan_generic<T, IS_MAX>(buffer, size,
     946             :                                                                 noDataValue);
     947             :         else
     948             :             return extremum_element_with_nan_generic<T, IS_MAX>(buffer, size);
     949             :     }
     950             :     else
     951             : #endif
     952         256 :         if (bHasNoData)
     953         130 :         return extremum_element<T, IS_MAX>(buffer, size, noDataValue);
     954             :     else
     955         126 :         return extremum_element<T, IS_MAX>(buffer, size);
     956             : }
     957             : 
     958             : template <class T, class NODATAType>
     959         119 : inline bool IsValueExactAs([[maybe_unused]] NODATAType noDataValue)
     960             : {
     961             :     if constexpr (std::is_same_v<T, NODATAType>)
     962         102 :         return true;
     963             :     else
     964          17 :         return GDALIsValueExactAs<T>(static_cast<double>(noDataValue));
     965             : }
     966             : 
     967             : template <bool IS_MAX, class NODATAType>
     968         226 : size_t extremum_element(const void *buffer, size_t nElts, GDALDataType eDT,
     969             :                         bool bHasNoData, NODATAType noDataValue)
     970             : {
     971         226 :     switch (eDT)
     972             :     {
     973             : #if GDAL_VERSION_NUM >= GDAL_COMPUTE_VERSION(3, 7, 0)
     974          15 :         case GDT_Int8:
     975             :         {
     976             :             using T = int8_t;
     977          15 :             bHasNoData = bHasNoData && IsValueExactAs<T>(noDataValue);
     978          15 :             return extremum_element<T, IS_MAX>(
     979             :                 static_cast<const T *>(buffer), nElts, bHasNoData,
     980          15 :                 bHasNoData ? static_cast<T>(noDataValue) : 0);
     981             :         }
     982             : #endif
     983          35 :         case GDT_Byte:
     984             :         {
     985             :             using T = uint8_t;
     986          35 :             bHasNoData = bHasNoData && IsValueExactAs<T>(noDataValue);
     987          35 :             return extremum_element<T, IS_MAX>(
     988             :                 static_cast<const T *>(buffer), nElts, bHasNoData,
     989          35 :                 bHasNoData ? static_cast<T>(noDataValue) : 0);
     990             :         }
     991          15 :         case GDT_Int16:
     992             :         {
     993             :             using T = int16_t;
     994          15 :             bHasNoData = bHasNoData && IsValueExactAs<T>(noDataValue);
     995          15 :             return extremum_element<T, IS_MAX>(
     996             :                 static_cast<const T *>(buffer), nElts, bHasNoData,
     997          15 :                 bHasNoData ? static_cast<T>(noDataValue) : 0);
     998             :         }
     999          15 :         case GDT_UInt16:
    1000             :         {
    1001             :             using T = uint16_t;
    1002          15 :             bHasNoData = bHasNoData && IsValueExactAs<T>(noDataValue);
    1003          15 :             return extremum_element<T, IS_MAX>(
    1004             :                 static_cast<const T *>(buffer), nElts, bHasNoData,
    1005          15 :                 bHasNoData ? static_cast<T>(noDataValue) : 0);
    1006             :         }
    1007          15 :         case GDT_Int32:
    1008             :         {
    1009             :             using T = int32_t;
    1010          15 :             bHasNoData = bHasNoData && IsValueExactAs<T>(noDataValue);
    1011          15 :             return extremum_element<T, IS_MAX>(
    1012             :                 static_cast<const T *>(buffer), nElts, bHasNoData,
    1013          15 :                 bHasNoData ? static_cast<T>(noDataValue) : 0);
    1014             :         }
    1015          15 :         case GDT_UInt32:
    1016             :         {
    1017             :             using T = uint32_t;
    1018          15 :             bHasNoData = bHasNoData && IsValueExactAs<T>(noDataValue);
    1019          15 :             return extremum_element<T, IS_MAX>(
    1020             :                 static_cast<const T *>(buffer), nElts, bHasNoData,
    1021          15 :                 bHasNoData ? static_cast<T>(noDataValue) : 0);
    1022             :         }
    1023             : #if GDAL_VERSION_NUM >= GDAL_COMPUTE_VERSION(3, 5, 0)
    1024          15 :         case GDT_Int64:
    1025             :         {
    1026             :             using T = int64_t;
    1027          15 :             bHasNoData = bHasNoData && IsValueExactAs<T>(noDataValue);
    1028          15 :             return extremum_element<T, IS_MAX>(
    1029             :                 static_cast<const T *>(buffer), nElts, bHasNoData,
    1030          15 :                 bHasNoData ? static_cast<T>(noDataValue) : 0);
    1031             :         }
    1032          15 :         case GDT_UInt64:
    1033             :         {
    1034             :             using T = uint64_t;
    1035          15 :             bHasNoData = bHasNoData && IsValueExactAs<T>(noDataValue);
    1036          15 :             return extremum_element<T, IS_MAX>(
    1037             :                 static_cast<const T *>(buffer), nElts, bHasNoData,
    1038          15 :                 bHasNoData ? static_cast<T>(noDataValue) : 0);
    1039             :         }
    1040             : #endif
    1041             : #if GDAL_VERSION_NUM >= GDAL_COMPUTE_VERSION(3, 11, 0)
    1042          30 :         case GDT_Float16:
    1043             :         {
    1044             :             using T = GFloat16;
    1045          30 :             bHasNoData = bHasNoData && IsValueExactAs<T>(noDataValue);
    1046          30 :             return extremum_element<T, IS_MAX>(
    1047             :                 static_cast<const T *>(buffer), nElts, bHasNoData,
    1048          30 :                 bHasNoData ? static_cast<T>(noDataValue) : static_cast<T>(0));
    1049             :         }
    1050             : #endif
    1051          29 :         case GDT_Float32:
    1052             :         {
    1053             :             using T = float;
    1054          29 :             bHasNoData = bHasNoData && IsValueExactAs<T>(noDataValue);
    1055          29 :             return extremum_element<T, IS_MAX>(
    1056             :                 static_cast<const T *>(buffer), nElts, bHasNoData,
    1057          29 :                 bHasNoData ? static_cast<T>(noDataValue) : 0);
    1058             :         }
    1059          25 :         case GDT_Float64:
    1060             :         {
    1061             :             using T = double;
    1062          25 :             bHasNoData = bHasNoData && IsValueExactAs<T>(noDataValue);
    1063          25 :             return extremum_element<T, IS_MAX>(
    1064             :                 static_cast<const T *>(buffer), nElts, bHasNoData,
    1065          25 :                 bHasNoData ? static_cast<T>(noDataValue) : 0);
    1066             :         }
    1067           2 :         case GDT_CInt16:
    1068             :         case GDT_CInt32:
    1069             : #if GDAL_VERSION_NUM >= GDAL_COMPUTE_VERSION(3, 11, 0)
    1070             :         case GDT_CFloat16:
    1071             : #endif
    1072             :         case GDT_CFloat32:
    1073             :         case GDT_CFloat64:
    1074             :         case GDT_Unknown:
    1075             :         case GDT_TypeCount:
    1076           2 :             break;
    1077             :     }
    1078           2 :     CPLError(CE_Failure, CPLE_NotSupported,
    1079             :              "%s not supported for this data type.", __FUNCTION__);
    1080           2 :     return 0;
    1081             : }
    1082             : 
    1083             : }  // namespace detail
    1084             : 
    1085             : /************************************************************************/
    1086             : /*                            max_element()                             */
    1087             : /************************************************************************/
    1088             : 
    1089             : /** Return the index of the element where the maximum value is hit.
    1090             :  *
    1091             :  * If it is hit in several locations, it is not specified which one will be
    1092             :  * returned.
    1093             :  *
    1094             :  * @param buffer Vector of nElts elements of type eDT.
    1095             :  * @param nElts Number of elements in buffer.
    1096             :  * @param eDT Data type of the elements of buffer.
    1097             :  * @param bHasNoData Whether noDataValue is valid.
    1098             :  * @param noDataValue Nodata value, only taken into account if bHasNoData == true
    1099             :  *
    1100             :  * @since GDAL 3.11
    1101             :  */
    1102             : template <class NODATAType>
    1103          97 : inline size_t max_element(const void *buffer, size_t nElts, GDALDataType eDT,
    1104             :                           bool bHasNoData, NODATAType noDataValue)
    1105             : {
    1106          97 :     return detail::extremum_element<true>(buffer, nElts, eDT, bHasNoData,
    1107          97 :                                           noDataValue);
    1108             : }
    1109             : 
    1110             : /************************************************************************/
    1111             : /*                            min_element()                             */
    1112             : /************************************************************************/
    1113             : 
    1114             : /** Return the index of the element where the minimum value is hit.
    1115             :  *
    1116             :  * If it is hit in several locations, it is not specified which one will be
    1117             :  * returned.
    1118             :  *
    1119             :  * @param buffer Vector of nElts elements of type eDT.
    1120             :  * @param nElts Number of elements in buffer.
    1121             :  * @param eDT Data type of the elements of buffer.
    1122             :  * @param bHasNoData Whether noDataValue is valid.
    1123             :  * @param noDataValue Nodata value, only taken into account if bHasNoData == true
    1124             :  *
    1125             :  * @since GDAL 3.11
    1126             :  */
    1127             : template <class NODATAType>
    1128         129 : inline size_t min_element(const void *buffer, size_t nElts, GDALDataType eDT,
    1129             :                           bool bHasNoData, NODATAType noDataValue)
    1130             : {
    1131         129 :     return detail::extremum_element<false>(buffer, nElts, eDT, bHasNoData,
    1132         129 :                                            noDataValue);
    1133             : }
    1134             : 
    1135             : namespace detail
    1136             : {
    1137             : 
    1138             : #ifdef NOT_EFFICIENT
    1139             : 
    1140             : /************************************************************************/
    1141             : /*                         minmax_element()                             */
    1142             : /************************************************************************/
    1143             : 
    1144             : template <class T>
    1145             : std::pair<size_t, size_t> minmax_element(const T *v, size_t size, T noDataValue)
    1146             : {
    1147             :     static_assert(!(std::is_floating_point_v<T>));
    1148             : #if GDAL_VERSION_NUM >= GDAL_COMPUTE_VERSION(3, 11, 0)
    1149             :     static_assert(!std::is_same_v<T, GFloat16>);
    1150             : #endif
    1151             :     if (size == 0)
    1152             :         return std::pair(0, 0);
    1153             :     size_t idx_of_min = 0;
    1154             :     size_t idx_of_max = 0;
    1155             :     T vmin = v[0];
    1156             :     T vmax = v[0];
    1157             :     bool extremum_is_nodata = vmin == noDataValue;
    1158             :     size_t i = 1;
    1159             :     for (; i < size; ++i)
    1160             :     {
    1161             :         if (v[i] != noDataValue && (v[i] < vmin || extremum_is_nodata))
    1162             :         {
    1163             :             vmin = v[i];
    1164             :             idx_of_min = i;
    1165             :             extremum_is_nodata = false;
    1166             :         }
    1167             :         if (v[i] != noDataValue && (v[i] > vmax || extremum_is_nodata))
    1168             :         {
    1169             :             vmax = v[i];
    1170             :             idx_of_max = i;
    1171             :             extremum_is_nodata = false;
    1172             :         }
    1173             :     }
    1174             :     return std::pair(idx_of_min, idx_of_max);
    1175             : }
    1176             : 
    1177             : template <class T>
    1178             : std::pair<size_t, size_t> minmax_element(const T *v, size_t size)
    1179             : {
    1180             :     static_assert(!(std::is_floating_point_v<T>));
    1181             : #if GDAL_VERSION_NUM >= GDAL_COMPUTE_VERSION(3, 11, 0)
    1182             :     static_assert(!std::is_same_v<T, GFloat16>);
    1183             : #endif
    1184             :     if (size == 0)
    1185             :         return std::pair(0, 0);
    1186             :     size_t idx_of_min = 0;
    1187             :     size_t idx_of_max = 0;
    1188             :     T vmin = v[0];
    1189             :     T vmax = v[0];
    1190             :     size_t i = 1;
    1191             :     for (; i < size; ++i)
    1192             :     {
    1193             :         if (v[i] < vmin)
    1194             :         {
    1195             :             vmin = v[i];
    1196             :             idx_of_min = i;
    1197             :         }
    1198             :         if (v[i] > vmax)
    1199             :         {
    1200             :             vmax = v[i];
    1201             :             idx_of_max = i;
    1202             :         }
    1203             :     }
    1204             :     return std::pair(idx_of_min, idx_of_max);
    1205             : }
    1206             : 
    1207             : template <class T>
    1208             : inline std::pair<size_t, size_t> minmax_element_with_nan(const T *v,
    1209             :                                                          size_t size)
    1210             : {
    1211             :     if (size == 0)
    1212             :         return std::pair(0, 0);
    1213             :     size_t idx_of_min = 0;
    1214             :     size_t idx_of_max = 0;
    1215             :     T vmin = v[0];
    1216             :     T vmax = v[0];
    1217             :     size_t i = 1;
    1218             :     if (IsNan(v[0]))
    1219             :     {
    1220             :         for (; i < size; ++i)
    1221             :         {
    1222             :             if (!IsNan(v[i]))
    1223             :             {
    1224             :                 vmin = v[i];
    1225             :                 idx_of_min = i;
    1226             :                 vmax = v[i];
    1227             :                 idx_of_max = i;
    1228             :                 break;
    1229             :             }
    1230             :         }
    1231             :     }
    1232             :     for (; i < size; ++i)
    1233             :     {
    1234             :         if (v[i] < vmin)
    1235             :         {
    1236             :             vmin = v[i];
    1237             :             idx_of_min = i;
    1238             :         }
    1239             :         if (v[i] > vmax)
    1240             :         {
    1241             :             vmax = v[i];
    1242             :             idx_of_max = i;
    1243             :         }
    1244             :     }
    1245             :     return std::pair(idx_of_min, idx_of_max);
    1246             : }
    1247             : 
    1248             : template <>
    1249             : std::pair<size_t, size_t> minmax_element<float>(const float *v, size_t size)
    1250             : {
    1251             :     return minmax_element_with_nan<float>(v, size);
    1252             : }
    1253             : 
    1254             : template <>
    1255             : std::pair<size_t, size_t> minmax_element<double>(const double *v, size_t size)
    1256             : {
    1257             :     return minmax_element_with_nan<double>(v, size);
    1258             : }
    1259             : 
    1260             : template <class T>
    1261             : inline std::pair<size_t, size_t> minmax_element(const T *buffer, size_t size,
    1262             :                                                 bool bHasNoData, T noDataValue)
    1263             : {
    1264             :     if (bHasNoData)
    1265             :     {
    1266             :         return minmax_element<T>(buffer, size, noDataValue);
    1267             :     }
    1268             :     else
    1269             :     {
    1270             :         return minmax_element<T>(buffer, size);
    1271             :     }
    1272             : }
    1273             : #else
    1274             : 
    1275             : /************************************************************************/
    1276             : /*                         minmax_element()                             */
    1277             : /************************************************************************/
    1278             : 
    1279             : template <class T>
    1280          16 : inline std::pair<size_t, size_t> minmax_element(const T *buffer, size_t size,
    1281             :                                                 bool bHasNoData, T noDataValue)
    1282             : {
    1283             : #ifdef NOT_EFFICIENT
    1284             :     if (bHasNoData)
    1285             :     {
    1286             :         return minmax_element<T>(buffer, size, noDataValue);
    1287             :     }
    1288             :     else
    1289             :     {
    1290             :         return minmax_element<T>(buffer, size);
    1291             :         //auto [imin, imax] = std::minmax_element(buffer, buffer + size);
    1292             :         //return std::pair(imin - buffer, imax - buffer);
    1293             :     }
    1294             : #else
    1295             : 
    1296             : #if !defined(GDAL_MINMAX_ELEMENT_USE_SSE2)
    1297             :     if constexpr (!std::is_floating_point_v<T>
    1298             : #if GDAL_VERSION_NUM >= GDAL_COMPUTE_VERSION(3, 11, 0)
    1299             :                   && !std::is_same_v<T, GFloat16>
    1300             : #endif
    1301             :     )
    1302             :     {
    1303             :         if (!bHasNoData)
    1304             :         {
    1305             :             auto [min_iter, max_iter] =
    1306             :                 std::minmax_element(buffer, buffer + size);
    1307             :             return std::pair(min_iter - buffer, max_iter - buffer);
    1308             :         }
    1309             :     }
    1310             : #endif
    1311             : 
    1312             :     // Using separately min and max is more efficient than computing them
    1313             :     // within the same loop
    1314             :     return std::pair(
    1315          48 :         extremum_element<T, false>(buffer, size, bHasNoData, noDataValue),
    1316          16 :         extremum_element<T, true>(buffer, size, bHasNoData, noDataValue));
    1317             : 
    1318             : #endif
    1319             : }
    1320             : #endif
    1321             : 
    1322             : }  // namespace detail
    1323             : 
    1324             : /************************************************************************/
    1325             : /*                          minmax_element()                            */
    1326             : /************************************************************************/
    1327             : 
    1328             : /** Return the index of the elements where the minimum and maximum values are hit.
    1329             :  *
    1330             :  * If they are hit in several locations, it is not specified which one will be
    1331             :  * returned (contrary to std::minmax_element).
    1332             :  *
    1333             :  * @param buffer Vector of nElts elements of type eDT.
    1334             :  * @param nElts Number of elements in buffer.
    1335             :  * @param eDT Data type of the elements of buffer.
    1336             :  * @param bHasNoData Whether noDataValue is valid.
    1337             :  * @param noDataValue Nodata value, only taken into account if bHasNoData == true
    1338             :  *
    1339             :  * @since GDAL 3.11
    1340             :  */
    1341             : template <class NODATAType>
    1342             : inline std::pair<size_t, size_t>
    1343          17 : minmax_element(const void *buffer, size_t nElts, GDALDataType eDT,
    1344             :                bool bHasNoData, NODATAType noDataValue)
    1345             : {
    1346          17 :     switch (eDT)
    1347             :     {
    1348             : #if GDAL_VERSION_NUM >= GDAL_COMPUTE_VERSION(3, 7, 0)
    1349           1 :         case GDT_Int8:
    1350             :         {
    1351             :             using T = int8_t;
    1352           1 :             bHasNoData = bHasNoData && detail::IsValueExactAs<T>(noDataValue);
    1353           1 :             return detail::minmax_element<T>(
    1354             :                 static_cast<const T *>(buffer), nElts, bHasNoData,
    1355           1 :                 bHasNoData ? static_cast<T>(noDataValue) : 0);
    1356             :         }
    1357             : #endif
    1358           5 :         case GDT_Byte:
    1359             :         {
    1360             :             using T = uint8_t;
    1361           5 :             bHasNoData = bHasNoData && detail::IsValueExactAs<T>(noDataValue);
    1362           5 :             return detail::minmax_element<T>(
    1363             :                 static_cast<const T *>(buffer), nElts, bHasNoData,
    1364           5 :                 bHasNoData ? static_cast<T>(noDataValue) : 0);
    1365             :         }
    1366           1 :         case GDT_Int16:
    1367             :         {
    1368             :             using T = int16_t;
    1369           1 :             bHasNoData = bHasNoData && detail::IsValueExactAs<T>(noDataValue);
    1370           1 :             return detail::minmax_element<T>(
    1371             :                 static_cast<const T *>(buffer), nElts, bHasNoData,
    1372           1 :                 bHasNoData ? static_cast<T>(noDataValue) : 0);
    1373             :         }
    1374           1 :         case GDT_UInt16:
    1375             :         {
    1376             :             using T = uint16_t;
    1377           1 :             bHasNoData = bHasNoData && detail::IsValueExactAs<T>(noDataValue);
    1378           1 :             return detail::minmax_element<T>(
    1379             :                 static_cast<const T *>(buffer), nElts, bHasNoData,
    1380           1 :                 bHasNoData ? static_cast<T>(noDataValue) : 0);
    1381             :         }
    1382           1 :         case GDT_Int32:
    1383             :         {
    1384             :             using T = int32_t;
    1385           1 :             bHasNoData = bHasNoData && detail::IsValueExactAs<T>(noDataValue);
    1386           1 :             return detail::minmax_element<T>(
    1387             :                 static_cast<const T *>(buffer), nElts, bHasNoData,
    1388           1 :                 bHasNoData ? static_cast<T>(noDataValue) : 0);
    1389             :         }
    1390           1 :         case GDT_UInt32:
    1391             :         {
    1392             :             using T = uint32_t;
    1393           1 :             bHasNoData = bHasNoData && detail::IsValueExactAs<T>(noDataValue);
    1394           1 :             return detail::minmax_element<T>(
    1395             :                 static_cast<const T *>(buffer), nElts, bHasNoData,
    1396           1 :                 bHasNoData ? static_cast<T>(noDataValue) : 0);
    1397             :         }
    1398             : #if GDAL_VERSION_NUM >= GDAL_COMPUTE_VERSION(3, 5, 0)
    1399           1 :         case GDT_Int64:
    1400             :         {
    1401             :             using T = int64_t;
    1402           1 :             bHasNoData = bHasNoData && detail::IsValueExactAs<T>(noDataValue);
    1403           1 :             return detail::minmax_element<T>(
    1404             :                 static_cast<const T *>(buffer), nElts, bHasNoData,
    1405           1 :                 bHasNoData ? static_cast<T>(noDataValue) : 0);
    1406             :         }
    1407           1 :         case GDT_UInt64:
    1408             :         {
    1409             :             using T = uint64_t;
    1410           1 :             bHasNoData = bHasNoData && detail::IsValueExactAs<T>(noDataValue);
    1411           1 :             return detail::minmax_element<T>(
    1412             :                 static_cast<const T *>(buffer), nElts, bHasNoData,
    1413           1 :                 bHasNoData ? static_cast<T>(noDataValue) : 0);
    1414             :         }
    1415             : #endif
    1416             : #if GDAL_VERSION_NUM >= GDAL_COMPUTE_VERSION(3, 11, 0)
    1417           1 :         case GDT_Float16:
    1418             :         {
    1419             :             using T = GFloat16;
    1420           1 :             bHasNoData = bHasNoData && detail::IsValueExactAs<T>(noDataValue);
    1421           1 :             return detail::minmax_element<T>(
    1422             :                 static_cast<const T *>(buffer), nElts, bHasNoData,
    1423           1 :                 bHasNoData ? static_cast<T>(noDataValue) : static_cast<T>(0));
    1424             :         }
    1425             : #endif
    1426           1 :         case GDT_Float32:
    1427             :         {
    1428             :             using T = float;
    1429           1 :             bHasNoData = bHasNoData && detail::IsValueExactAs<T>(noDataValue);
    1430           1 :             return detail::minmax_element<T>(
    1431             :                 static_cast<const T *>(buffer), nElts, bHasNoData,
    1432           1 :                 bHasNoData ? static_cast<T>(noDataValue) : 0);
    1433             :         }
    1434           2 :         case GDT_Float64:
    1435             :         {
    1436             :             using T = double;
    1437           2 :             bHasNoData = bHasNoData && detail::IsValueExactAs<T>(noDataValue);
    1438           2 :             return detail::minmax_element<T>(
    1439             :                 static_cast<const T *>(buffer), nElts, bHasNoData,
    1440           2 :                 bHasNoData ? static_cast<T>(noDataValue) : 0);
    1441             :         }
    1442           1 :         case GDT_CInt16:
    1443             :         case GDT_CInt32:
    1444             : #if GDAL_VERSION_NUM >= GDAL_COMPUTE_VERSION(3, 11, 0)
    1445             :         case GDT_CFloat16:
    1446             : #endif
    1447             :         case GDT_CFloat32:
    1448             :         case GDT_CFloat64:
    1449             :         case GDT_Unknown:
    1450             :         case GDT_TypeCount:
    1451           1 :             break;
    1452             :     }
    1453           1 :     CPLError(CE_Failure, CPLE_NotSupported,
    1454             :              "%s not supported for this data type.", __FUNCTION__);
    1455           1 :     return std::pair(0, 0);
    1456             : }
    1457             : 
    1458             : }  // namespace GDAL_MINMAXELT_NS
    1459             : 
    1460             : #endif  // GDAL_MINMAX_ELEMENT_INCLUDED

Generated by: LCOV version 1.14