[graphite] Add a fast SIMD Rect type

Implements a Rect type whose standard operations use float4. The intent
of this class is to make calculations on draw bounds as fast as
possible.

Change-Id: I3bdb219b242bb7097809507c345b613670ff386e
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/457136
Commit-Queue: Chris Dalton <csmartdalton@google.com>
Reviewed-by: Michael Ludwig <michaelludwig@google.com>
diff --git a/experimental/graphite/include/GraphiteTypes.h b/experimental/graphite/include/GraphiteTypes.h
index 2f7fbad..882a5c7 100644
--- a/experimental/graphite/include/GraphiteTypes.h
+++ b/experimental/graphite/include/GraphiteTypes.h
@@ -9,9 +9,23 @@
 #define skgpu_GraphiteTypes_DEFINED
 
 #include "include/core/SkTypes.h"
+#include "include/private/SkVx.h"
 
 namespace skgpu {
 
+// Use familiar type names from SkSL.
+template<int N> using vec = skvx::Vec<N, float>;
+using float2 = vec<2>;
+using float4 = vec<4>;
+
+template<int N> using ivec = skvx::Vec<N, int32_t>;
+using int2 = ivec<2>;
+using int4 = ivec<4>;
+
+template<int N> using uvec = skvx::Vec<N, uint32_t>;
+using uint2 = uvec<2>;
+using uint4 = uvec<4>;
+
 /**
  * Wraps an enum that is used for flags, and enables masking with type safety. Example:
  *
diff --git a/experimental/graphite/src/geom/Rect.h b/experimental/graphite/src/geom/Rect.h
new file mode 100644
index 0000000..1ae4d4a
--- /dev/null
+++ b/experimental/graphite/src/geom/Rect.h
@@ -0,0 +1,127 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef skgpu_geom_Rect_DEFINED
+#define skgpu_geom_Rect_DEFINED
+
+#include "experimental/graphite/include/GraphiteTypes.h"
+#include "include/core/SkRect.h"
+
+namespace skgpu {
+
+#define AI SK_ALWAYS_INLINE
+
+/**
+ * SIMD rect implementation. Vales are stored internally in the form: [left, top, -right, -bot].
+ *
+ * Some operations (e.g., intersect, inset) may return a negative or empty rect
+ * (negative meaning, left >= right or top >= bot).
+ *
+ * Operations on a rect that is either negative or empty, while well-defined, might not give the
+ * intended result. It is the caller's responsibility to check isEmptyOrNegative() if needed.
+ */
+class Rect {
+public:
+    AI Rect() = default;
+    AI Rect(float l, float t, float r, float b) : fVals(NegateBotRight({l,t,r,b})) {}
+    AI Rect(float2 topLeft, float2 botRight) : fVals(topLeft, -botRight) {}
+    AI Rect(const SkRect& r) : fVals(NegateBotRight(float4::Load(r.asScalars()))) {}
+
+    AI static Rect XYWH(float x, float y, float w, float h) {
+        return Rect(x, y, x + w, y + h);
+    }
+    AI static Rect XYWH(float2 topLeft, float2 size) {
+        return Rect(topLeft, topLeft + size);
+    }
+    AI static Rect WH(float w, float h) {
+        return Rect(0, 0, w, h);
+    }
+    AI static Rect WH(float2 size) {
+        return Rect(float2(0), size);
+    }
+    AI static Rect FromVals(float4 vals) {  // vals.zw must already be negated.
+        return Rect(vals);
+    }
+
+    AI bool operator==(Rect rect) const { return all(fVals == rect.fVals); }
+    AI bool operator!=(Rect rect) const { return any(fVals != rect.fVals); }
+
+    AI float x() const { return fVals.x(); }
+    AI float y() const { return fVals.y(); }
+    AI float left() const { return fVals.x(); }
+    AI float top() const { return fVals.y(); }
+    AI float right() const { return -fVals.z(); }
+    AI float bot() const { return -fVals.w(); }
+    AI float2 topLeft() const { return fVals.xy(); }
+    AI float2 botRight() const { return -fVals.zw(); }
+    AI float4 ltrb() const { return NegateBotRight(fVals); }
+    AI float4 vals() const { return fVals; }  // [left, top, -right, -bot].
+
+    AI bool isEmptyOrNegative() const {
+        return any(fVals.xy() + fVals.zw() >= 0);  // == ([l-r, r-b] >= 0) == ([w, h] <= 0)
+    }
+
+    AI float2 size() const { return -(fVals.xy() + fVals.zw()); }  // == [-(l-r), -(t-b)] == [w, h]
+
+    AI float2 center() const {
+        float4 p = fVals * float4(.5f, .5f, -.5f, -.5f);  // == [l, t, r, b] * .5
+        return p.xy() + p.zw();  // == [(l + r)/2, (t + b)/2]
+    }
+
+    AI float area() const {
+        float2 negativeSize = fVals.xy() + fVals.zw();  // == [l-r, t-b] == [-w, -h]
+        return negativeSize.x() * negativeSize.y();
+    }
+
+    // A rect stored in a complementary form of: [right, bottom, -left, -top]. Store a local
+    // ComplementRect object if intersects() will be called many times.
+    struct ComplementRect {
+        AI ComplementRect(Rect rect) : fVals(-rect.fVals.zwxy()) {}
+        float4 fVals;  // [right, bottom, -left, -top]
+    };
+
+    AI bool intersects(ComplementRect comp) const { return all(fVals < comp.fVals); }
+    AI bool contains(Rect rect) const { return all(fVals <= rect.fVals); }
+
+    // Some operations may return a negative or empty rect. Operations on a rect that either is
+    // negative or empty, while well-defined, might not give the intended result. It is the caller's
+    // responsibility to check isEmptyOrNegative() if needed.
+    AI Rect makeRoundIn() const { return ceil(fVals); }
+    AI Rect makeRoundOut() const { return floor(fVals); }
+    AI Rect makeInset(float inset) const { return fVals + inset; }
+    AI Rect makeInset(float2 inset) const { return fVals + inset.xyxy(); }
+    AI Rect makeOutset(float outset) const { return fVals - outset; }
+    AI Rect makeOutset(float2 outset) const { return fVals - outset.xyxy(); }
+    AI Rect makeOffset(float2 offset) const { return fVals + float4(offset, -offset); }
+    AI Rect makeJoin(Rect rect) const { return min(fVals, rect.fVals); }
+    AI Rect makeIntersect(const Rect& rect) const { return max(fVals, rect.fVals); }
+
+    AI Rect& roundIn() { return *this = this->makeRoundIn(); }
+    AI Rect& roundOut() { return *this = this->makeRoundOut(); }
+    AI Rect& inset(float inset) { return *this = this->makeInset(inset); }
+    AI Rect& inset(float2 inset) { return *this = this->makeInset(inset); }
+    AI Rect& outset(float outset) { return *this = this->makeOutset(outset); }
+    AI Rect& outset(float2 outset) { return *this = this->makeOutset(outset); }
+    AI Rect& offset(float2 offset) { return *this = this->makeOffset(offset); }
+    AI Rect& join(Rect rect) { return *this = this->makeJoin(rect); }
+    AI Rect& intersect(Rect& rect) { return *this = this->makeIntersect(rect); }
+
+private:
+    AI static float4 NegateBotRight(float4 vals) {  // Returns [vals.xy, -vals.zw].
+        return skvx::bit_pun<float4>(skvx::bit_pun<uint4>(vals) ^ uint4(0, 0, 1u << 31, 1u << 31));
+    }
+
+    AI Rect(float4 vals) : fVals(vals) {}  // vals.zw must already be negated.
+
+    float4 fVals;  // [left, top, -right, -bottom]
+};
+
+#undef AI
+
+} // namespace skgpu
+
+#endif // skgpu_geom_Rect_DEFINED
diff --git a/gn/tests.gni b/gn/tests.gni
index 75894c8..4e9b865 100644
--- a/gn/tests.gni
+++ b/gn/tests.gni
@@ -397,6 +397,7 @@
 
   # graphite
   "$_tests/graphite/MaskTest.cpp",
+  "$_tests/graphite/RectTest.cpp",
 ]
 
 skgpu_v1_tests_sources = [
diff --git a/tests/graphite/RectTest.cpp b/tests/graphite/RectTest.cpp
new file mode 100644
index 0000000..0d4ee2b
--- /dev/null
+++ b/tests/graphite/RectTest.cpp
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2021 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "experimental/graphite/src/geom/Rect.h"
+#include "tests/Test.h"
+
+namespace skgpu {
+
+#define CHECK(A) REPORTER_ASSERT(reporter, A)
+
+DEF_TEST(skgpu_Rect, reporter) {
+    const SkRect skRect = SkRect::MakeLTRB(1,-3,4,0);
+    const Rect rect = skRect;
+    CHECK(rect == rect);
+    CHECK(rect == skRect);
+
+    for (const float l : {0,1,2}) {
+    for (const float t : {-4,-3,-2}) {
+    for (const float r : {3,4,5}) {
+    for (const float b : {-1,0,1}) {
+        const Rect rect2(l,t,r,b);
+        const SkRect skRect2{l,t,r,b};
+
+        CHECK(rect2 == rect2);
+        CHECK(rect2 == Rect(float2(l,t), float2(r,b)));
+        CHECK(rect2 == Rect(skRect2));
+
+        CHECK((rect2 == rect) == (rect == rect2));
+        CHECK((rect2 != rect) == (rect != rect2));
+        CHECK((rect != rect2) == !(rect == rect2));
+
+        CHECK(rect2 == Rect::XYWH(l, t, r - l, b - t));
+        CHECK(rect2 == Rect::XYWH(float2(l, t), float2(r - l, b - t)));
+        if (l == 0 && t == 0) {
+            CHECK(rect2 == Rect::WH(r - l, b - t));
+            CHECK(rect2 == Rect::WH(float2(r - l, b - t)));
+        }
+        CHECK(rect2 == Rect::FromVals(rect2.vals()));
+
+        CHECK(rect2.x() == l);
+        CHECK(rect2.y() == t);
+        CHECK(rect2.left() == l);
+        CHECK(rect2.top() == t);
+        CHECK(rect2.right() == r);
+        CHECK(rect2.bot() == b);
+        CHECK(all(rect2.topLeft() == float2(l,t)));
+        CHECK(all(rect2.botRight() == float2(r,b)));
+        CHECK(all(rect2.ltrb() == float4(l,t,r,b)));
+        CHECK(all(rect2.vals() == float4(l,t,-r,-b)));
+
+        CHECK(all(rect2.size() == float2(skRect2.width(), skRect2.height())));
+        CHECK(all(rect2.center() == float2(skRect2.centerX(), skRect2.centerY())));
+        CHECK(rect2.area() == skRect2.height() * skRect2.width());
+
+        CHECK(rect.intersects(rect2) == rect2.intersects(rect));
+        CHECK(rect.intersects(rect2) == skRect.intersects(skRect2));
+        CHECK(rect.contains(rect2) == skRect.contains(skRect2));
+        CHECK(rect2.contains(rect) == skRect2.contains(skRect));
+
+        CHECK(rect2.makeRoundIn() == SkRect::Make(skRect2.roundIn()));
+        CHECK(rect2.makeRoundOut() == SkRect::Make(skRect2.roundOut()));
+        CHECK(rect2.makeInset(.5f) == skRect2.makeInset(.5f, .5f));
+        CHECK(rect2.makeInset({.5f, -.25f}) == skRect2.makeInset(.5f, -.25f));
+        CHECK(rect2.makeOutset(.5f) == skRect2.makeOutset(.5f, .5f));
+        CHECK(rect2.makeOutset({.5f, -.25f}) == skRect2.makeOutset(.5f, -.25f));
+        CHECK(rect2.makeOffset({.5f, -.25f}) == skRect2.makeOffset(.5f, -.25f));
+
+        SkRect skJoin = skRect;
+        skJoin.join(skRect2);
+        CHECK(rect.makeJoin(rect2) == skJoin);
+        CHECK(rect.makeJoin(rect2) == rect2.makeJoin(rect));
+
+        CHECK(rect.intersects(rect2) == !rect.makeIntersect(rect2).isEmptyOrNegative());
+        CHECK(rect.makeIntersect(rect2) == rect2.makeIntersect(rect));
+        if (rect.intersects(rect2)) {
+            CHECK(skRect.intersects(skRect2));
+            SkRect skIsect;
+            CHECK(skIsect.intersect(skRect, skRect2));
+            CHECK(rect.makeIntersect(rect2) == Rect(skIsect));
+        }
+    }}}}
+}
+
+}  // namespace skgpu