Implement Sk4f min/max

Bug: skia:
Change-Id: Icf235dea81e9f125c1c8590ec87cb3591393036c
Reviewed-on: https://skia-review.googlesource.com/120281
Commit-Queue: Mike Klein <mtklein@google.com>
Reviewed-by: Mike Klein <mtklein@google.com>
diff --git a/src/core/SkNx.h b/src/core/SkNx.h
index 0b41365..11c8f7c 100644
--- a/src/core/SkNx.h
+++ b/src/core/SkNx.h
@@ -103,6 +103,8 @@
         Half::Store2(ptr + 2*N/2*sizeof(T), a.fHi, b.fHi);
     }
 
+    AI T min() const { return std::min(fLo.min(), fHi.min()); }
+    AI T max() const { return std::max(fLo.max(), fHi.max()); }
     AI bool anyTrue() const { return fLo.anyTrue() || fHi.anyTrue(); }
     AI bool allTrue() const { return fLo.allTrue() && fHi.allTrue(); }
 
@@ -211,6 +213,8 @@
         b.store(ptr + 1*sizeof(T));
     }
 
+    AI T min() const { return fVal; }
+    AI T max() const { return fVal; }
     AI bool anyTrue() const { return fVal != 0; }
     AI bool allTrue() const { return fVal != 0; }
 
diff --git a/src/opts/SkNx_neon.h b/src/opts/SkNx_neon.h
index 232903a..9020036 100644
--- a/src/opts/SkNx_neon.h
+++ b/src/opts/SkNx_neon.h
@@ -244,6 +244,24 @@
         return pun.fs[k&3];
     }
 
+    AI float min() const {
+    #if defined(__aarch64__)
+        return vminvq_f32(fVec);
+    #else
+        SkNx min = Min(*this, vrev64q_f32(fVec));
+        return std::min(min[0], min[2]);
+    #endif
+    }
+
+    AI float max() const {
+    #if defined(__aarch64__)
+        return vmaxvq_f32(fVec);
+    #else
+        SkNx max = Max(*this, vrev64q_f32(fVec));
+        return std::max(max[0], max[2]);
+    #endif
+    }
+
     AI bool allTrue() const {
     #if defined(__aarch64__)
         return 0 != vminvq_u32(vreinterpretq_u32_f32(fVec));
diff --git a/src/opts/SkNx_sse.h b/src/opts/SkNx_sse.h
index bd5c58e..00edc3a 100644
--- a/src/opts/SkNx_sse.h
+++ b/src/opts/SkNx_sse.h
@@ -176,6 +176,18 @@
         return pun.fs[k&3];
     }
 
+    AI float min() const {
+        SkNx min = Min(*this, _mm_shuffle_ps(fVec, fVec, _MM_SHUFFLE(2,3,0,1)));
+        min = Min(min, _mm_shuffle_ps(min.fVec, min.fVec, _MM_SHUFFLE(0,1,2,3)));
+        return min[0];
+    }
+
+    AI float max() const {
+        SkNx max = Max(*this, _mm_shuffle_ps(fVec, fVec, _MM_SHUFFLE(2,3,0,1)));
+        max = Max(max, _mm_shuffle_ps(max.fVec, max.fVec, _MM_SHUFFLE(0,1,2,3)));
+        return max[0];
+    }
+
     AI bool allTrue() const { return 0xffff == _mm_movemask_epi8(_mm_castps_si128(fVec)); }
     AI bool anyTrue() const { return 0x0000 != _mm_movemask_epi8(_mm_castps_si128(fVec)); }
 
diff --git a/tests/SkNxTest.cpp b/tests/SkNxTest.cpp
index c27b88c..c82f39d 100644
--- a/tests/SkNxTest.cpp
+++ b/tests/SkNxTest.cpp
@@ -466,6 +466,18 @@
     REPORTER_ASSERT(r, dst[7] == 7);
 }
 
+DEF_TEST(Sk4f_minmax, r) {
+    REPORTER_ASSERT(r,  3 == Sk4f(0,1,2,3).max());
+    REPORTER_ASSERT(r,  2 == Sk4f(1,-5,2,-1).max());
+    REPORTER_ASSERT(r, -1 == Sk4f(-2,-1,-6,-3).max());
+    REPORTER_ASSERT(r,  3 == Sk4f(3,2,1,0).max());
+
+    REPORTER_ASSERT(r,  0 == Sk4f(0,1,2,3).min());
+    REPORTER_ASSERT(r, -5 == Sk4f(1,-5,2,-1).min());
+    REPORTER_ASSERT(r, -6 == Sk4f(-2,-1,-6,-3).min());
+    REPORTER_ASSERT(r,  0 == Sk4f(3,2,1,0).min());
+}
+
 DEF_TEST(SkNf_anyTrue_allTrue, r) {
     REPORTER_ASSERT(r,  (Sk2f{1,2} < Sk2f{3,4}).anyTrue());
     REPORTER_ASSERT(r,  (Sk2f{1,2} < Sk2f{3,4}).allTrue());