[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