diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /gfx/tests/gtest | |
parent | Initial commit. (diff) | |
download | firefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz firefox-26a029d407be480d791972afb5975cf62c9360a6.zip |
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'gfx/tests/gtest')
25 files changed, 8274 insertions, 0 deletions
diff --git a/gfx/tests/gtest/MockWidget.cpp b/gfx/tests/gtest/MockWidget.cpp new file mode 100644 index 0000000000..696845641e --- /dev/null +++ b/gfx/tests/gtest/MockWidget.cpp @@ -0,0 +1,8 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "MockWidget.h" + +NS_IMPL_ISUPPORTS_INHERITED0(MockWidget, nsBaseWidget) diff --git a/gfx/tests/gtest/MockWidget.h b/gfx/tests/gtest/MockWidget.h new file mode 100644 index 0000000000..a8cbaf7702 --- /dev/null +++ b/gfx/tests/gtest/MockWidget.h @@ -0,0 +1,96 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GTEST_MOCKWIDGET_H +#define GTEST_MOCKWIDGET_H + +#include "mozilla/gfx/Point.h" +#include "mozilla/widget/InProcessCompositorWidget.h" +#include "nsBaseWidget.h" +#include "GLContext.h" +#include "GLContextProvider.h" + +using mozilla::gl::CreateContextFlags; +using mozilla::gl::GLContext; +using mozilla::gl::GLContextProvider; + +using mozilla::gfx::IntSize; + +class MockWidget : public nsBaseWidget { + public: + MockWidget() : mCompWidth(0), mCompHeight(0) {} + MockWidget(int aWidth, int aHeight) + : mCompWidth(aWidth), mCompHeight(aHeight) {} + NS_DECL_ISUPPORTS_INHERITED + + virtual LayoutDeviceIntRect GetClientBounds() override { + return LayoutDeviceIntRect(0, 0, mCompWidth, mCompHeight); + } + virtual LayoutDeviceIntRect GetBounds() override { return GetClientBounds(); } + + void* GetNativeData(uint32_t aDataType) override { + if (aDataType == NS_NATIVE_OPENGL_CONTEXT) { + nsCString discardFailureId; + RefPtr<GLContext> context = GLContextProvider::CreateHeadless( + {CreateContextFlags::REQUIRE_COMPAT_PROFILE}, &discardFailureId); + if (!context) { + return nullptr; + } + if (!context->CreateOffscreenDefaultFb({mCompWidth, mCompHeight})) { + return nullptr; + } + return context.forget().take(); + } + return nullptr; + } + + virtual nsresult Create(nsIWidget* aParent, nsNativeWidget aNativeParent, + const LayoutDeviceIntRect& aRect, + InitData* aInitData = nullptr) override { + return NS_OK; + } + virtual nsresult Create(nsIWidget* aParent, nsNativeWidget aNativeParent, + const DesktopIntRect& aRect, + InitData* aInitData = nullptr) override { + return NS_OK; + } + virtual void Show(bool aState) override {} + virtual bool IsVisible() const override { return true; } + virtual void Move(double aX, double aY) override {} + virtual void Resize(double aWidth, double aHeight, bool aRepaint) override {} + virtual void Resize(double aX, double aY, double aWidth, double aHeight, + bool aRepaint) override {} + + virtual void Enable(bool aState) override {} + virtual bool IsEnabled() const override { return true; } + + virtual nsSizeMode SizeMode() override { return mSizeMode; } + virtual void SetSizeMode(nsSizeMode aMode) override { mSizeMode = aMode; } + + virtual void SetFocus(Raise, mozilla::dom::CallerType aCallerType) override {} + virtual void Invalidate(const LayoutDeviceIntRect& aRect) override {} + virtual nsresult SetTitle(const nsAString& title) override { return NS_OK; } + virtual LayoutDeviceIntPoint WidgetToScreenOffset() override { + return LayoutDeviceIntPoint(0, 0); + } + virtual nsresult DispatchEvent(mozilla::WidgetGUIEvent* aEvent, + nsEventStatus& aStatus) override { + return NS_OK; + } + virtual void SetInputContext(const InputContext& aContext, + const InputContextAction& aAction) override {} + virtual InputContext GetInputContext() override { abort(); } + + private: + ~MockWidget() = default; + + nsSizeMode mSizeMode = nsSizeMode_Normal; + + int mCompWidth; + int mCompHeight; +}; + +#endif diff --git a/gfx/tests/gtest/PolygonTestUtils.cpp b/gfx/tests/gtest/PolygonTestUtils.cpp new file mode 100644 index 0000000000..04a2f47498 --- /dev/null +++ b/gfx/tests/gtest/PolygonTestUtils.cpp @@ -0,0 +1,163 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "PolygonTestUtils.h" + +#include <cmath> + +#include "Point.h" +#include "Triangle.h" + +typedef mozilla::gfx::Polygon MozPolygon; + +using mozilla::gfx::Point; +using mozilla::gfx::Point4D; +using mozilla::gfx::Triangle; + +namespace mozilla { +namespace gfx { + +const float kEpsilon = 0.001f; + +// Compares two points while allowing some numerical inaccuracy. +bool FuzzyEquals(const Point4D& lhs, const Point4D& rhs) { + const auto d = lhs - rhs; + + return std::abs(d.x) < kEpsilon && std::abs(d.y) < kEpsilon && + std::abs(d.z) < kEpsilon && std::abs(d.w) < kEpsilon; +} + +bool FuzzyEquals(const Point3D& lhs, const Point3D& rhs) { + const auto d = lhs - rhs; + + return std::abs(d.x) < kEpsilon && std::abs(d.y) < kEpsilon && + std::abs(d.z) < kEpsilon; +} + +bool FuzzyEquals(const Point& lhs, const Point& rhs) { + const auto d = lhs - rhs; + + return std::abs(d.x) < kEpsilon && std::abs(d.y) < kEpsilon; +} + +bool operator==(const Triangle& lhs, const Triangle& rhs) { + return FuzzyEquals(lhs.p1, rhs.p1) && FuzzyEquals(lhs.p2, rhs.p2) && + FuzzyEquals(lhs.p3, rhs.p3); +} + +// Compares the points of two polygons and ensures +// that the points are in the same winding order. +bool operator==(const MozPolygon& lhs, const MozPolygon& rhs) { + const auto& left = lhs.GetPoints(); + const auto& right = rhs.GetPoints(); + + // Polygons do not have the same amount of points. + if (left.Length() != right.Length()) { + return false; + } + + const size_t pointCount = left.Length(); + + // Find the first vertex of the first polygon from the second polygon. + // This assumes that the polygons do not contain duplicate vertices. + int start = -1; + for (size_t i = 0; i < pointCount; ++i) { + if (FuzzyEquals(left[0], right[i])) { + start = i; + break; + } + } + + // Found at least one different vertex. + if (start == -1) { + return false; + } + + // Verify that the polygons have the same points. + for (size_t i = 0; i < pointCount; ++i) { + size_t j = (start + i) % pointCount; + + if (!FuzzyEquals(left[i], right[j])) { + return false; + } + } + + return true; +} + +} // namespace gfx +} // namespace mozilla + +TEST(PolygonTestUtils, TestSanity) +{ + EXPECT_TRUE(FuzzyEquals(Point4D(0.0f, 0.0f, 0.0f, 1.0f), + Point4D(0.0f, 0.0f, 0.0f, 1.0f))); + + EXPECT_TRUE(FuzzyEquals(Point4D(0.0f, 0.0f, 0.0f, 1.0f), + Point4D(0.00001f, 0.00001f, 0.00001f, 1.0f))); + + EXPECT_TRUE(FuzzyEquals(Point4D(0.00001f, 0.00001f, 0.00001f, 1.0f), + Point4D(0.0f, 0.0f, 0.0f, 1.0f))); + + EXPECT_FALSE(FuzzyEquals(Point4D(0.0f, 0.0f, 0.0f, 1.0f), + Point4D(0.01f, 0.01f, 0.01f, 1.0f))); + + EXPECT_FALSE(FuzzyEquals(Point4D(0.01f, 0.01f, 0.01f, 1.0f), + Point4D(0.0f, 0.0f, 0.0f, 1.0f))); + + MozPolygon p1{ + Point4D(0.0f, 0.0f, 1.0f, 1.0f), Point4D(1.0f, 0.0f, 1.0f, 1.0f), + Point4D(1.0f, 1.0f, 1.0f, 1.0f), Point4D(0.0f, 1.0f, 1.0f, 1.0f)}; + + // Same points as above shifted forward by one position. + MozPolygon shifted{ + Point4D(0.0f, 1.0f, 1.0f, 1.0f), Point4D(0.0f, 0.0f, 1.0f, 1.0f), + Point4D(1.0f, 0.0f, 1.0f, 1.0f), Point4D(1.0f, 1.0f, 1.0f, 1.0f)}; + + MozPolygon p2{Point4D(0.00001f, 0.00001f, 1.00001f, 1.0f), + Point4D(1.00001f, 0.00001f, 1.00001f, 1.0f), + Point4D(1.00001f, 1.00001f, 1.00001f, 1.0f), + Point4D(0.00001f, 1.00001f, 1.00001f, 1.0f)}; + + MozPolygon p3{ + Point4D(0.01f, 0.01f, 1.01f, 1.0f), Point4D(1.01f, 0.01f, 1.01f, 1.0f), + Point4D(1.01f, 1.01f, 1.01f, 1.0f), Point4D(0.01f, 1.01f, 1.01f, 1.0f)}; + + // Trivial equals + EXPECT_TRUE(p1 == p1); + EXPECT_TRUE(p2 == p2); + EXPECT_TRUE(p3 == p3); + EXPECT_TRUE(shifted == shifted); + + // Polygons with the same point order + EXPECT_TRUE(p1 == p2); + EXPECT_TRUE(p1 == shifted); + + // Polygons containing different points + EXPECT_FALSE(p1 == p3); + EXPECT_FALSE(p2 == p3); + EXPECT_FALSE(shifted == p3); + + const nsTArray<Triangle> t1{ + Triangle(Point(0.0f, 0.0f), Point(0.0f, 1.0f), Point(1.0f, 1.0f))}; + + const nsTArray<Triangle> t2{ + Triangle(Point(0.0f, 0.0f), Point(1.0f, 1.0f), Point(1.0f, 0.0f))}; + + const nsTArray<Triangle> t3{Triangle(Point(0.00001f, 0.00001f), + Point(0.00001f, 1.00001f), + Point(1.00001f, 1.00001f))}; + + EXPECT_TRUE(t1[0] == t1[0]); + EXPECT_TRUE(t2[0] == t2[0]); + EXPECT_TRUE(t3[0] == t1[0]); + + EXPECT_FALSE(t1[0] == t2[0]); + EXPECT_FALSE(t2[0] == t3[0]); + + AssertArrayEQ(t1, t1); + AssertArrayEQ(t2, t2); +} diff --git a/gfx/tests/gtest/PolygonTestUtils.h b/gfx/tests/gtest/PolygonTestUtils.h new file mode 100644 index 0000000000..e860b6a8d1 --- /dev/null +++ b/gfx/tests/gtest/PolygonTestUtils.h @@ -0,0 +1,40 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_TEST_POLYGONUTILS_H +#define GFX_TEST_POLYGONUTILS_H + +#include "gtest/gtest.h" + +#include "nsTArray.h" +#include "Point.h" +#include "Polygon.h" +#include "Triangle.h" + +namespace mozilla { +namespace gfx { + +bool FuzzyEquals(const Point4D& lhs, const Point4D& rhs); +bool FuzzyEquals(const Point3D& lhs, const Point3D& rhs); +bool FuzzyEquals(const Point& lhs, const Point& rhs); + +bool operator==(const Triangle& lhs, const Triangle& rhs); +bool operator==(const Polygon& lhs, const Polygon& rhs); + +// Compares two arrays with the equality operator. +template <typename T> +void AssertArrayEQ(const nsTArray<T>& rhs, const nsTArray<T>& lhs) { + ASSERT_EQ(lhs.Length(), rhs.Length()); + + for (size_t i = 0; i < lhs.Length(); ++i) { + EXPECT_TRUE(lhs[i] == rhs[i]); + } +} + +} // namespace gfx +} // namespace mozilla + +#endif /* GFX_TEST_POLYGONUTILS_H */ diff --git a/gfx/tests/gtest/TestArena.cpp b/gfx/tests/gtest/TestArena.cpp new file mode 100644 index 0000000000..7f9aad6592 --- /dev/null +++ b/gfx/tests/gtest/TestArena.cpp @@ -0,0 +1,185 @@ +/* vim:set ts=2 sw=2 sts=2 et: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +#include "gtest/gtest.h" +#include "gmock/gmock.h" + +#include "mozilla/gfx/IterableArena.h" +#include <string> + +using namespace mozilla; +using namespace mozilla::gfx; + +#ifdef A +# undef A +#endif + +#ifdef B +# undef B +#endif + +// to avoid having symbols that collide easily like A and B in the global +// namespace +namespace test_arena { + +class A; +class B; + +class Base { + public: + virtual ~Base() = default; + virtual A* AsA() { return nullptr; } + virtual B* AsB() { return nullptr; } +}; + +static int sDtorItemA = 0; +static int sDtorItemB = 0; + +class A : public Base { + public: + virtual A* AsA() override { return this; } + + explicit A(uint64_t val) : mVal(val) {} + ~A() { ++sDtorItemA; } + + uint64_t mVal; +}; + +class B : public Base { + public: + virtual B* AsB() override { return this; } + + explicit B(const std::string& str) : mVal(str) {} + ~B() { ++sDtorItemB; } + + std::string mVal; +}; + +struct BigStruct { + uint64_t mVal; + uint8_t data[120]; + + explicit BigStruct(uint64_t val) : mVal(val) {} +}; + +static void TestArenaAlloc(IterableArena::ArenaType aType) { + sDtorItemA = 0; + sDtorItemB = 0; + IterableArena arena(aType, 256); + + // An empty arena has no items to iterate over. + { + int iterations = 0; + arena.ForEach([&](void* item) { iterations++; }); + ASSERT_EQ(iterations, 0); + } + + auto a1 = arena.Alloc<A>(42); + auto b1 = arena.Alloc<B>("Obladi oblada"); + auto a2 = arena.Alloc<A>(1337); + auto b2 = arena.Alloc<B>("Yellow submarine"); + auto b3 = arena.Alloc<B>("She's got a ticket to ride"); + + // Alloc returns a non-negative offset if the allocation succeeded. + ASSERT_TRUE(a1 >= 0); + ASSERT_TRUE(a2 >= 0); + ASSERT_TRUE(b1 >= 0); + ASSERT_TRUE(b2 >= 0); + ASSERT_TRUE(b3 >= 0); + + ASSERT_TRUE(arena.GetStorage(a1) != nullptr); + ASSERT_TRUE(arena.GetStorage(a2) != nullptr); + ASSERT_TRUE(arena.GetStorage(b1) != nullptr); + ASSERT_TRUE(arena.GetStorage(b2) != nullptr); + ASSERT_TRUE(arena.GetStorage(b3) != nullptr); + + ASSERT_TRUE(((Base*)arena.GetStorage(a1))->AsA() != nullptr); + ASSERT_TRUE(((Base*)arena.GetStorage(a2))->AsA() != nullptr); + + ASSERT_TRUE(((Base*)arena.GetStorage(b1))->AsB() != nullptr); + ASSERT_TRUE(((Base*)arena.GetStorage(b2))->AsB() != nullptr); + ASSERT_TRUE(((Base*)arena.GetStorage(b3))->AsB() != nullptr); + + ASSERT_EQ(((Base*)arena.GetStorage(a1))->AsA()->mVal, (uint64_t)42); + ASSERT_EQ(((Base*)arena.GetStorage(a2))->AsA()->mVal, (uint64_t)1337); + + ASSERT_EQ(((Base*)arena.GetStorage(b1))->AsB()->mVal, + std::string("Obladi oblada")); + ASSERT_EQ(((Base*)arena.GetStorage(b2))->AsB()->mVal, + std::string("Yellow submarine")); + ASSERT_EQ(((Base*)arena.GetStorage(b3))->AsB()->mVal, + std::string("She's got a ticket to ride")); + + { + int iterations = 0; + arena.ForEach([&](void* item) { iterations++; }); + ASSERT_EQ(iterations, 5); + } + + // Typically, running the destructors of the elements in the arena will is + // done manually like this: + arena.ForEach([](void* item) { ((Base*)item)->~Base(); }); + arena.Clear(); + ASSERT_EQ(sDtorItemA, 2); + ASSERT_EQ(sDtorItemB, 3); + + // An empty arena has no items to iterate over (we just cleared it). + { + int iterations = 0; + arena.ForEach([&](void* item) { iterations++; }); + ASSERT_EQ(iterations, 0); + } +} + +static void TestArenaLimit(IterableArena::ArenaType aType, + bool aShouldReachLimit) { + IterableArena arena(aType, 128); + + // A non-growable arena should return a negative offset when running out + // of space, without crashing. + // We should not run out of space with a growable arena (unless the os is + // running out of memory but this isn't expected for this test). + bool reachedLimit = false; + for (int i = 0; i < 100; ++i) { + auto offset = arena.Alloc<A>(42); + if (offset < 0) { + reachedLimit = true; + break; + } + } + ASSERT_EQ(reachedLimit, aShouldReachLimit); +} + +} // namespace test_arena + +using namespace test_arena; + +TEST(Moz2D, FixedArena) +{ + TestArenaAlloc(IterableArena::FIXED_SIZE); + TestArenaLimit(IterableArena::FIXED_SIZE, true); +} + +TEST(Moz2D, GrowableArena) +{ + TestArenaAlloc(IterableArena::GROWABLE); + TestArenaLimit(IterableArena::GROWABLE, false); + + IterableArena arena(IterableArena::GROWABLE, 16); + // sizeof(BigStruct) is more than twice the initial capacity, make sure that + // this doesn't blow everything up, since the arena doubles its storage size + // each time it grows (until it finds a size that fits). + auto a = arena.Alloc<BigStruct>(1); + auto b = arena.Alloc<BigStruct>(2); + auto c = arena.Alloc<BigStruct>(3); + + // Offsets should also still point to the appropriate values after + // reallocation. + ASSERT_EQ(((BigStruct*)arena.GetStorage(a))->mVal, (uint64_t)1); + ASSERT_EQ(((BigStruct*)arena.GetStorage(b))->mVal, (uint64_t)2); + ASSERT_EQ(((BigStruct*)arena.GetStorage(c))->mVal, (uint64_t)3); + + arena.Clear(); +} diff --git a/gfx/tests/gtest/TestArrayView.cpp b/gfx/tests/gtest/TestArrayView.cpp new file mode 100644 index 0000000000..209f286845 --- /dev/null +++ b/gfx/tests/gtest/TestArrayView.cpp @@ -0,0 +1,20 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include <limits> + +#include "gtest/gtest.h" + +#include "mozilla/ArrayView.h" + +using namespace mozilla::gfx; + +TEST(Gfx, ArrayView) +{ + nsTArray<int> p = {5, 6}; + ArrayView<int> pv(p); + ASSERT_EQ(pv[1], 6); + ASSERT_EQ(*pv.Data(), 5); +} diff --git a/gfx/tests/gtest/TestBSPTree.cpp b/gfx/tests/gtest/TestBSPTree.cpp new file mode 100644 index 0000000000..8601169a03 --- /dev/null +++ b/gfx/tests/gtest/TestBSPTree.cpp @@ -0,0 +1,694 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "gtest/gtest.h" + +#include "BSPTree.h" +#include "Polygon.h" +#include "PolygonTestUtils.h" + +#include <deque> +#include <list> + +using namespace mozilla::gfx; +using namespace mozilla::layers; +typedef mozilla::gfx::Polygon MozPolygon; + +namespace { + +static void RunTest(std::deque<MozPolygon> aPolygons, + std::deque<MozPolygon> aExpected) { + std::list<TestPolygon> layers; + for (MozPolygon& polygon : aPolygons) { + layers.push_back(TestPolygon(nullptr, std::move(polygon))); + } + + const BSPTree tree(layers); + const nsTArray<TestPolygon> order = tree.GetDrawOrder(); + + EXPECT_EQ(aExpected.size(), order.Length()); + + for (size_t i = 0; i < order.Length(); ++i) { + EXPECT_TRUE(aExpected[i] == *order[i].geometry); + } +} + +} // namespace + +TEST(BSPTree, SameNode) +{ + const std::deque<MozPolygon> polygons{ + MozPolygon{ + Point4D(0.0f, 0.0f, 0.0f, 1.0f), Point4D(1.0f, 0.0f, 0.0f, 1.0f), + Point4D(1.0f, 1.0f, 0.0f, 1.0f), Point4D(0.0f, 1.0f, 0.0f, 1.0f)}, + MozPolygon{ + Point4D(0.0f, 0.0f, 0.0f, 1.0f), Point4D(1.0f, 0.0f, 0.0f, 1.0f), + Point4D(1.0f, 1.0f, 0.0f, 1.0f), Point4D(0.0f, 1.0f, 0.0f, 1.0f)}}; + + ::RunTest(polygons, polygons); +} + +TEST(BSPTree, OneChild) +{ + const MozPolygon p1{ + Point4D(0.0f, 0.0f, 0.0f, 1.0f), Point4D(1.0f, 0.0f, 0.0f, 1.0f), + Point4D(1.0f, 1.0f, 0.0f, 1.0f), Point4D(0.0f, 1.0f, 0.0f, 1.0f)}; + + const MozPolygon p2{ + Point4D(0.0f, 0.0f, 1.0f, 1.0f), Point4D(1.0f, 0.0f, 1.0f, 1.0f), + Point4D(1.0f, 1.0f, 1.0f, 1.0f), Point4D(0.0f, 1.0f, 1.0f, 1.0f)}; + + ::RunTest({p1, p2}, {p1, p2}); + ::RunTest({p2, p1}, {p1, p2}); +} + +TEST(BSPTree, SharedEdge1) +{ + MozPolygon p1{ + Point4D(1.0f, 0.0f, 1.0f, 1.0f), Point4D(0.0f, 0.0f, 1.0f, 1.0f), + Point4D(0.0f, 1.0f, 1.0f, 1.0f), Point4D(1.0f, 1.0f, 1.0f, 1.0f)}; + + MozPolygon p2{ + Point4D(1.0f, 0.0f, 1.0f, 1.0f), Point4D(1.0f, 1.0f, 1.0f, 1.0f), + Point4D(2.0f, 2.0f, 1.0f, 1.0f), Point4D(2.0f, 0.0f, 1.0f, 1.0f)}; + + ::RunTest({p1, p2}, {p1, p2}); +} + +TEST(BSPTree, SharedEdge2) +{ + MozPolygon p1{ + Point4D(1.0f, 0.0f, 1.0f, 1.0f), Point4D(0.0f, 0.0f, 1.0f, 1.0f), + Point4D(0.0f, 1.0f, 1.0f, 1.0f), Point4D(1.0f, 1.0f, 1.0f, 1.0f)}; + + MozPolygon p2{ + Point4D(1.0f, 0.0f, 1.0f, 1.0f), Point4D(1.0f, 1.0f, 1.0f, 1.0f), + Point4D(2.0f, 2.0f, 0.0f, 1.0f), Point4D(2.0f, 0.0f, 0.0f, 1.0f)}; + + ::RunTest({p1, p2}, {p2, p1}); +} + +TEST(BSPTree, SplitSharedEdge) +{ + MozPolygon p1{ + Point4D(1.0f, 0.0f, 1.0f, 1.0f), Point4D(0.0f, 0.0f, 1.0f, 1.0f), + Point4D(0.0f, 1.0f, 1.0f, 1.0f), Point4D(1.0f, 1.0f, 1.0f, 1.0f)}; + + MozPolygon p2{ + Point4D(1.0f, 0.0f, 2.0f, 1.0f), Point4D(1.0f, 1.0f, 2.0f, 1.0f), + Point4D(1.0f, 1.0f, 0.0f, 1.0f), Point4D(1.0f, 0.0f, 0.0f, 1.0f)}; + + const std::deque<MozPolygon> expected{ + MozPolygon{ + Point4D(1.0f, 1.0f, 1.0f, 1.0f), Point4D(1.0f, 1.0f, 0.0f, 1.0f), + Point4D(1.0f, 0.0f, 0.0f, 1.0f), Point4D(1.0f, 0.0f, 1.0f, 1.0f)}, + MozPolygon{ + Point4D(1.0f, 0.0f, 1.0f, 1.0f), Point4D(0.0f, 0.0f, 1.0f, 1.0f), + Point4D(0.0f, 1.0f, 1.0f, 1.0f), Point4D(1.0f, 1.0f, 1.0f, 1.0f)}, + MozPolygon{ + Point4D(1.0f, 0.0f, 2.0f, 1.0f), Point4D(1.0f, 1.0f, 2.0f, 1.0f), + Point4D(1.0f, 1.0f, 1.0f, 1.0f), Point4D(1.0f, 0.0f, 1.0f, 1.0f)}}; + + ::RunTest({p1, p2}, expected); +} + +TEST(BSPTree, SplitSimple1) +{ + MozPolygon p1{ + Point4D(0.0f, 0.0f, 1.0f, 1.0f), Point4D(1.0f, 0.0f, 1.0f, 1.0f), + Point4D(1.0f, 1.0f, 1.0f, 1.0f), Point4D(0.0f, 1.0f, 1.0f, 1.0f)}; + + MozPolygon p2{ + Point4D(0.0f, 0.0f, 2.0f, 1.0f), Point4D(1.0f, 0.0f, 2.0f, 1.0f), + Point4D(1.0f, 1.0f, 0.0f, 1.0f), Point4D(0.0f, 1.0f, 0.0f, 1.0f)}; + + const std::deque<MozPolygon> expected{ + MozPolygon{ + Point4D(0.0f, 1.0f, 0.0f, 1.0f), Point4D(0.0f, 0.5f, 1.0f, 1.0f), + Point4D(1.0f, 0.5f, 1.0f, 1.0f), Point4D(1.0f, 1.0f, 0.0f, 1.0f)}, + p1, + MozPolygon{ + Point4D(0.0f, 0.0f, 2.0f, 1.0f), Point4D(1.0f, 0.0f, 2.0f, 1.0f), + Point4D(1.0f, 0.5f, 1.0f, 1.0f), Point4D(0.0f, 0.5f, 1.0f, 1.0f)}}; + + ::RunTest({p1, p2}, expected); +} + +TEST(BSPTree, SplitSimple2) +{ + const std::deque<MozPolygon> polygons{ + MozPolygon{Point4D(-5.00000f, -5.00000f, 0.00000f, 1.0f), + Point4D(-5.00000f, 5.00000f, 0.00000f, 1.0f), + Point4D(5.00000f, 5.00000f, 0.00000f, 1.0f), + Point4D(5.00000f, -5.00000f, 0.00000f, 1.0f)}, + MozPolygon{Point4D(0.00000f, -5.00000f, -5.00000f, 1.0f), + Point4D(0.00000f, 5.00000f, -5.00000f, 1.0f), + Point4D(0.00000f, 5.00000f, 5.00000f, 1.0f), + Point4D(0.00000f, -5.00000f, 5.00000f, 1.0f)}}; + + const std::deque<MozPolygon> expected{ + MozPolygon{Point4D(0.00000f, -5.00000f, 0.00000f, 1.0f), + Point4D(0.00000f, -5.00000f, -5.00000f, 1.0f), + Point4D(0.00000f, 5.00000f, -5.00000f, 1.0f), + Point4D(0.00000f, 5.00000f, 0.00000f, 1.0f)}, + MozPolygon{Point4D(-5.00000f, -5.00000f, 0.00000f, 1.0f), + Point4D(-5.00000f, 5.00000f, 0.00000f, 1.0f), + Point4D(5.00000f, 5.00000f, 0.00000f, 1.0f), + Point4D(5.00000f, -5.00000f, 0.00000f, 1.0f)}, + MozPolygon{Point4D(0.00000f, 5.00000f, 0.00000f, 1.0f), + Point4D(0.00000f, 5.00000f, 5.00000f, 1.0f), + Point4D(0.00000f, -5.00000f, 5.00000f, 1.0f), + Point4D(0.00000f, -5.00000f, 0.00000f, 1.0f)}}; + ::RunTest(polygons, expected); +} + +TEST(BSPTree, NoSplit1) +{ + const std::deque<MozPolygon> polygons{ + MozPolygon{Point4D(0.00000f, 10.00000f, 0.00000f, 1.0f), + Point4D(0.00000f, 0.00000f, 0.00000f, 1.0f), + Point4D(10.00000f, 0.00000f, 0.00000f, 1.0f), + Point4D(10.00000f, 10.00000f, 0.00000f, 1.0f)}, + MozPolygon{Point4D(0.00000f, 10.00000f, -5.00000f, 1.0f), + Point4D(0.00000f, 0.00000f, -5.00000f, 1.0f), + Point4D(10.00000f, 0.00000f, -5.00000f, 1.0f), + Point4D(10.00000f, 10.00000f, -5.00000f, 1.0f)}, + MozPolygon{Point4D(0.00000f, 10.00000f, 5.00000f, 1.0f), + Point4D(0.00000f, 0.00000f, 5.00000f, 1.0f), + Point4D(10.00000f, 0.00000f, 5.00000f, 1.0f), + Point4D(10.00000f, 10.00000f, 5.00000f, 1.0f)}}; + + const std::deque<MozPolygon> expected{ + MozPolygon{Point4D(0.00000f, 10.00000f, -5.00000f, 1.0f), + Point4D(0.00000f, 0.00000f, -5.00000f, 1.0f), + Point4D(10.00000f, 0.00000f, -5.00000f, 1.0f), + Point4D(10.00000f, 10.00000f, -5.00000f, 1.0f)}, + MozPolygon{Point4D(0.00000f, 10.00000f, 0.00000f, 1.0f), + Point4D(0.00000f, 0.00000f, 0.00000f, 1.0f), + Point4D(10.00000f, 0.00000f, 0.00000f, 1.0f), + Point4D(10.00000f, 10.00000f, 0.00000f, 1.0f)}, + MozPolygon{Point4D(0.00000f, 10.00000f, 5.00000f, 1.0f), + Point4D(0.00000f, 0.00000f, 5.00000f, 1.0f), + Point4D(10.00000f, 0.00000f, 5.00000f, 1.0f), + Point4D(10.00000f, 10.00000f, 5.00000f, 1.0f)}}; + ::RunTest(polygons, expected); +} + +TEST(BSPTree, NoSplit2) +{ + const std::deque<MozPolygon> polygons{ + MozPolygon{Point4D(-5.00000f, -5.00000f, 0.00000f, 1.0f), + Point4D(-5.00000f, 5.00000f, 0.00000f, 1.0f), + Point4D(5.00000f, 5.00000f, 0.00000f, 1.0f), + Point4D(5.00000f, -5.00000f, 0.00000f, 1.0f)}, + MozPolygon{Point4D(0.00000f, 5.00000f, -15.00000f, 1.0f), + Point4D(0.00000f, -5.00000f, -15.00000f, 1.0f), + Point4D(0.00000f, -5.00000f, -10.00000f, 1.0f), + Point4D(0.00000f, 5.00000f, -10.00000f, 1.0f)}}; + + const std::deque<MozPolygon> expected{ + MozPolygon{Point4D(0.00000f, 5.00000f, -15.00000f, 1.0f), + Point4D(0.00000f, -5.00000f, -15.00000f, 1.0f), + Point4D(0.00000f, -5.00000f, -10.00000f, 1.0f), + Point4D(0.00000f, 5.00000f, -10.00000f, 1.0f)}, + MozPolygon{Point4D(-5.00000f, -5.00000f, 0.00000f, 1.0f), + Point4D(-5.00000f, 5.00000f, 0.00000f, 1.0f), + Point4D(5.00000f, 5.00000f, 0.00000f, 1.0f), + Point4D(5.00000f, -5.00000f, 0.00000f, 1.0f)}}; + ::RunTest(polygons, expected); +} + +TEST(BSPTree, TwoPlaneIntersectRotate0degrees) +{ + const std::deque<MozPolygon> polygons{ + MozPolygon{Point4D(-0.00000f, 2.00000f, 2.00000f, 1.0f), + Point4D(-0.00000f, -2.00000f, 2.00000f, 1.0f), + Point4D(0.00010f, -2.00000f, -2.00000f, 1.0f), + Point4D(0.00010f, 2.00000f, -2.00000f, 1.0f)}, + MozPolygon{Point4D(2.00000f, 0.00000f, 2.00000f, 1.0f), + Point4D(2.00000f, -0.00000f, -2.00000f, 1.0f), + Point4D(-2.00000f, 0.00000f, -2.00000f, 1.0f), + Point4D(-2.00000f, 0.00010f, 2.00000f, 1.0f)}}; + + const std::deque<MozPolygon> expected{ + MozPolygon{Point4D(2.00000f, 0.00000f, 2.00000f, 1.0f), + Point4D(2.00000f, -0.00000f, -2.00000f, 1.0f), + Point4D(-2.00000f, 0.00000f, -2.00000f, 1.0f), + Point4D(-2.00000f, 0.00010f, 2.00000f, 1.0f)}, + MozPolygon{Point4D(-0.00000f, 2.00000f, 2.00000f, 1.0f), + Point4D(-0.00000f, -2.00000f, 2.00000f, 1.0f), + Point4D(0.00010f, -2.00000f, -2.00000f, 1.0f), + Point4D(0.00010f, 2.00000f, -2.00000f, 1.0f)}}; + ::RunTest(polygons, expected); +} + +TEST(BSPTree, TwoPlaneIntersectRotate20degrees) +{ + const std::deque<MozPolygon> polygons{ + MozPolygon{Point4D(-0.00000f, 1.19540f, 2.56350f, 1.0f), + Point4D(-0.00000f, -2.56340f, 1.19540f, 1.0f), + Point4D(0.00010f, -1.19530f, -2.56340f, 1.0f), + Point4D(0.00010f, 2.56350f, -1.19530f, 1.0f)}, + MozPolygon{Point4D(2.00000f, -0.68400f, 1.87940f, 1.0f), + Point4D(2.00000f, 0.68410f, -1.87930f, 1.0f), + Point4D(-2.00000f, 0.68410f, -1.87930f, 1.0f), + Point4D(-2.00000f, -0.68400f, 1.87940f, 1.0f)}}; + + const std::deque<MozPolygon> expected{ + MozPolygon{Point4D(2.00000f, -0.68400f, 1.87940f, 1.0f), + Point4D(2.00000f, 0.68410f, -1.87930f, 1.0f), + Point4D(-2.00000f, 0.68410f, -1.87930f, 1.0f), + Point4D(-2.00000f, -0.68400f, 1.87940f, 1.0f)}, + MozPolygon{Point4D(-0.00000f, 1.19540f, 2.56350f, 1.0f), + Point4D(-0.00000f, -2.56340f, 1.19540f, 1.0f), + Point4D(0.00010f, -1.19530f, -2.56340f, 1.0f), + Point4D(0.00010f, 2.56350f, -1.19530f, 1.0f)}}; + ::RunTest(polygons, expected); +} + +TEST(BSPTree, TwoPlaneIntersectRotate40degrees) +{ + const std::deque<MozPolygon> polygons{ + MozPolygon{Point4D(-0.00000f, -0.73200f, 2.73210f, 1.0f), + Point4D(-0.00000f, -2.73200f, -0.73200f, 1.0f), + Point4D(0.00010f, 0.73210f, -2.73200f, 1.0f), + Point4D(0.00010f, 2.73210f, 0.73210f, 1.0f)}, + MozPolygon{Point4D(2.00000f, -1.73200f, 1.00000f, 1.0f), + Point4D(2.00000f, 1.73210f, -0.99990f, 1.0f), + Point4D(-2.00000f, 1.73210f, -0.99990f, 1.0f), + Point4D(-2.00000f, -1.73200f, 1.00000f, 1.0f)}}; + + const std::deque<MozPolygon> expected{ + MozPolygon{Point4D(2.00000f, -1.73200f, 1.00000f, 1.0f), + Point4D(2.00000f, 1.73210f, -0.99990f, 1.0f), + Point4D(-2.00000f, 1.73210f, -0.99990f, 1.0f), + Point4D(-2.00000f, -1.73200f, 1.00000f, 1.0f)}, + MozPolygon{Point4D(-0.00000f, -0.73200f, 2.73210f, 1.0f), + Point4D(-0.00000f, -2.73200f, -0.73200f, 1.0f), + Point4D(0.00010f, 0.73210f, -2.73200f, 1.0f), + Point4D(0.00010f, 2.73210f, 0.73210f, 1.0f)}}; + ::RunTest(polygons, expected); +} + +TEST(BSPTree, TwoPlaneIntersectRotate60degrees) +{ + const std::deque<MozPolygon> polygons{ + MozPolygon{Point4D(-0.00000f, -2.73200f, 0.73210f, 1.0f), + Point4D(-0.00000f, -0.73200f, -2.73200f, 1.0f), + Point4D(0.00010f, 2.73210f, -0.73200f, 1.0f), + Point4D(0.00010f, 0.73210f, 2.73210f, 1.0f)}, + MozPolygon{Point4D(2.00000f, -1.73200f, -1.00000f, 1.0f), + Point4D(2.00000f, 1.73210f, 1.00010f, 1.0f), + Point4D(-2.00000f, 1.73210f, 1.00010f, 1.0f), + Point4D(-2.00000f, -1.73200f, -1.00000f, 1.0f)}}; + + const std::deque<MozPolygon> expected{ + MozPolygon{Point4D(-2.00000f, 1.26793f, 0.73210f, 1.0f), + Point4D(-2.00000f, -1.73200f, -1.00000f, 1.0f), + Point4D(2.00000f, -1.73200f, -1.00000f, 1.0f), + Point4D(2.00000f, 1.26793f, 0.73210f, 1.0f)}, + MozPolygon{Point4D(-0.00000f, -2.73200f, 0.73210f, 1.0f), + Point4D(-0.00000f, -0.73200f, -2.73200f, 1.0f), + Point4D(0.00010f, 2.73210f, -0.73200f, 1.0f), + Point4D(0.00010f, 0.73210f, 2.73210f, 1.0f)}, + MozPolygon{Point4D(2.00000f, 1.26793f, 0.73210f, 1.0f), + Point4D(2.00000f, 1.73210f, 1.00010f, 1.0f), + Point4D(-2.00000f, 1.73210f, 1.00010f, 1.0f), + Point4D(-2.00000f, 1.26793f, 0.73210f, 1.0f)}}; + ::RunTest(polygons, expected); +} + +TEST(BSPTree, TwoPlaneIntersectRotate80degrees) +{ + const std::deque<MozPolygon> polygons{ + MozPolygon{Point4D(-0.00000f, -1.19530f, -2.56340f, 1.0f), + Point4D(-0.00000f, 2.56350f, -1.19530f, 1.0f), + Point4D(0.00010f, 1.19540f, 2.56350f, 1.0f), + Point4D(0.00010f, -2.56340f, 1.19540f, 1.0f)}, + MozPolygon{Point4D(2.00000f, 0.68410f, -1.87930f, 1.0f), + Point4D(2.00000f, -0.68400f, 1.87940f, 1.0f), + Point4D(-2.00000f, -0.68400f, 1.87940f, 1.0f), + Point4D(-2.00000f, 0.68410f, -1.87930f, 1.0f)}}; + + const std::deque<MozPolygon> expected{ + MozPolygon{Point4D(-0.00000f, -1.19530f, -2.56340f, 1.0f), + Point4D(-0.00000f, 2.56350f, -1.19530f, 1.0f), + Point4D(0.00010f, 1.19540f, 2.56350f, 1.0f), + Point4D(0.00010f, -2.56340f, 1.19540f, 1.0f)}, + MozPolygon{Point4D(2.00000f, 0.68410f, -1.87930f, 1.0f), + Point4D(2.00000f, -0.68400f, 1.87940f, 1.0f), + Point4D(-2.00000f, -0.68400f, 1.87940f, 1.0f), + Point4D(-2.00000f, 0.68410f, -1.87930f, 1.0f)}}; + ::RunTest(polygons, expected); +} + +TEST(BSPTree, TwoPlaneIntersectRotate100degrees) +{ + const std::deque<MozPolygon> polygons{ + MozPolygon{Point4D(-0.00000f, 2.73210f, -0.73200f, 1.0f), + Point4D(-0.00000f, 0.73210f, 2.73210f, 1.0f), + Point4D(0.00010f, -2.73200f, 0.73210f, 1.0f), + Point4D(0.00010f, -0.73200f, -2.73200f, 1.0f)}, + MozPolygon{Point4D(2.00000f, 1.73210f, 1.00010f, 1.0f), + Point4D(2.00000f, -1.73200f, -1.00000f, 1.0f), + Point4D(-2.00000f, -1.73200f, -1.00000f, 1.0f), + Point4D(-2.00000f, 1.73210f, 1.00010f, 1.0f)}}; + + const std::deque<MozPolygon> expected{ + MozPolygon{Point4D(2.00000f, -1.26783f, -0.73200f, 1.0f), + Point4D(2.00000f, -1.73200f, -1.00000f, 1.0f), + Point4D(-2.00000f, -1.73200f, -1.00000f, 1.0f), + Point4D(-2.00000f, -1.26783f, -0.73200f, 1.0f)}, + MozPolygon{Point4D(-0.00000f, 2.73210f, -0.73200f, 1.0f), + Point4D(-0.00000f, 0.73210f, 2.73210f, 1.0f), + Point4D(0.00010f, -2.73200f, 0.73210f, 1.0f), + Point4D(0.00010f, -0.73200f, -2.73200f, 1.0f)}, + MozPolygon{Point4D(-2.00000f, -1.26783f, -0.73200f, 1.0f), + Point4D(-2.00000f, 1.73210f, 1.00010f, 1.0f), + Point4D(2.00000f, 1.73210f, 1.00010f, 1.0f), + Point4D(2.00000f, -1.26783f, -0.73200f, 1.0f)}}; + ::RunTest(polygons, expected); +} + +TEST(BSPTree, TwoPlaneIntersectRotate120degrees) +{ + const std::deque<MozPolygon> polygons{ + MozPolygon{Point4D(-0.00000f, -0.73200f, 2.73210f, 1.0f), + Point4D(-0.00000f, -2.73200f, -0.73200f, 1.0f), + Point4D(0.00010f, 0.73210f, -2.73200f, 1.0f), + Point4D(0.00010f, 2.73210f, 0.73210f, 1.0f)}, + MozPolygon{Point4D(2.00000f, -1.73200f, 1.00000f, 1.0f), + Point4D(2.00000f, 1.73210f, -0.99990f, 1.0f), + Point4D(-2.00000f, 1.73210f, -0.99990f, 1.0f), + Point4D(-2.00000f, -1.73200f, 1.00000f, 1.0f)}}; + + const std::deque<MozPolygon> expected{ + MozPolygon{Point4D(2.00000f, -1.73200f, 1.00000f, 1.0f), + Point4D(2.00000f, 1.73210f, -0.99990f, 1.0f), + Point4D(-2.00000f, 1.73210f, -0.99990f, 1.0f), + Point4D(-2.00000f, -1.73200f, 1.00000f, 1.0f)}, + MozPolygon{Point4D(-0.00000f, -0.73200f, 2.73210f, 1.0f), + Point4D(-0.00000f, -2.73200f, -0.73200f, 1.0f), + Point4D(0.00010f, 0.73210f, -2.73200f, 1.0f), + Point4D(0.00010f, 2.73210f, 0.73210f, 1.0f)}}; + ::RunTest(polygons, expected); +} + +TEST(BSPTree, TwoPlaneIntersectRotate140degrees) +{ + const std::deque<MozPolygon> polygons{ + MozPolygon{Point4D(-0.00000f, -1.19530f, -2.56340f, 1.0f), + Point4D(-0.00000f, 2.56350f, -1.19530f, 1.0f), + Point4D(0.00010f, 1.19540f, 2.56350f, 1.0f), + Point4D(0.00010f, -2.56340f, 1.19540f, 1.0f)}, + MozPolygon{Point4D(2.00000f, 0.68410f, -1.87930f, 1.0f), + Point4D(2.00000f, -0.68400f, 1.87940f, 1.0f), + Point4D(-2.00000f, -0.68400f, 1.87940f, 1.0f), + Point4D(-2.00000f, 0.68410f, -1.87930f, 1.0f)}}; + + const std::deque<MozPolygon> expected{ + MozPolygon{Point4D(-0.00000f, -1.19530f, -2.56340f, 1.0f), + Point4D(-0.00000f, 2.56350f, -1.19530f, 1.0f), + Point4D(0.00010f, 1.19540f, 2.56350f, 1.0f), + Point4D(0.00010f, -2.56340f, 1.19540f, 1.0f)}, + MozPolygon{Point4D(2.00000f, 0.68410f, -1.87930f, 1.0f), + Point4D(2.00000f, -0.68400f, 1.87940f, 1.0f), + Point4D(-2.00000f, -0.68400f, 1.87940f, 1.0f), + Point4D(-2.00000f, 0.68410f, -1.87930f, 1.0f)}}; + ::RunTest(polygons, expected); +} + +TEST(BSPTree, TwoPlaneIntersectRotate160degrees) +{ + const std::deque<MozPolygon> polygons{ + MozPolygon{Point4D(-0.00000f, 2.00000f, 2.00000f, 1.0f), + Point4D(-0.00000f, -2.00000f, 2.00000f, 1.0f), + Point4D(0.00010f, -2.00000f, -2.00000f, 1.0f), + Point4D(0.00010f, 2.00000f, -2.00000f, 1.0f)}, + MozPolygon{Point4D(2.00000f, -0.00000f, 2.00000f, 1.0f), + Point4D(2.00000f, 0.00010f, -2.00000f, 1.0f), + Point4D(-2.00000f, 0.00010f, -2.00000f, 1.0f), + Point4D(-2.00000f, -0.00000f, 2.00000f, 1.0f)}}; + + const std::deque<MozPolygon> expected{ + MozPolygon{Point4D(2.00000f, -0.00000f, 2.00000f, 1.0f), + Point4D(2.00000f, 0.00010f, -2.00000f, 1.0f), + Point4D(-2.00000f, 0.00010f, -2.00000f, 1.0f), + Point4D(-2.00000f, -0.00000f, 2.00000f, 1.0f)}, + MozPolygon{Point4D(-0.00000f, 2.00000f, 2.00000f, 1.0f), + Point4D(-0.00000f, -2.00000f, 2.00000f, 1.0f), + Point4D(0.00010f, -2.00000f, -2.00000f, 1.0f), + Point4D(0.00010f, 2.00000f, -2.00000f, 1.0f)}}; + ::RunTest(polygons, expected); +} + +TEST(BSPTree, TwoPlaneIntersectRotate180degrees) +{ + const std::deque<MozPolygon> polygons{ + MozPolygon{Point4D(-0.00000f, -2.00000f, -2.00000f, 1.0f), + Point4D(-0.00000f, 2.00000f, -2.00000f, 1.0f), + Point4D(0.00010f, 2.00000f, 2.00000f, 1.0f), + Point4D(0.00010f, -2.00000f, 2.00000f, 1.0f)}, + MozPolygon{Point4D(2.00000f, 0.00010f, -2.00000f, 1.0f), + Point4D(2.00000f, -0.00000f, 2.00000f, 1.0f), + Point4D(-2.00000f, -0.00000f, 2.00000f, 1.0f), + Point4D(-2.00000f, 0.00010f, -2.00000f, 1.0f)}}; + + const std::deque<MozPolygon> expected{ + MozPolygon{Point4D(-0.00000f, -2.00000f, -2.00000f, 1.0f), + Point4D(-0.00000f, 2.00000f, -2.00000f, 1.0f), + Point4D(0.00010f, 2.00000f, 2.00000f, 1.0f), + Point4D(0.00010f, -2.00000f, 2.00000f, 1.0f)}, + MozPolygon{Point4D(2.00000f, 0.00010f, -2.00000f, 1.0f), + Point4D(2.00000f, -0.00000f, 2.00000f, 1.0f), + Point4D(-2.00000f, -0.00000f, 2.00000f, 1.0f), + Point4D(-2.00000f, 0.00010f, -2.00000f, 1.0f)}}; + ::RunTest(polygons, expected); +} + +TEST(BSPTree, TwoPlaneIntersectRotate200degrees) +{ + const std::deque<MozPolygon> polygons{ + MozPolygon{Point4D(-0.00000f, 1.19540f, 2.56350f, 1.0f), + Point4D(-0.00000f, -2.56340f, 1.19540f, 1.0f), + Point4D(0.00010f, -1.19530f, -2.56340f, 1.0f), + Point4D(0.00010f, 2.56350f, -1.19530f, 1.0f)}, + MozPolygon{Point4D(2.00000f, -0.68400f, 1.87940f, 1.0f), + Point4D(2.00000f, 0.68410f, -1.87930f, 1.0f), + Point4D(-2.00000f, 0.68410f, -1.87930f, 1.0f), + Point4D(-2.00000f, -0.68400f, 1.87940f, 1.0f)}}; + + const std::deque<MozPolygon> expected{ + MozPolygon{Point4D(2.00000f, -0.68400f, 1.87940f, 1.0f), + Point4D(2.00000f, 0.68410f, -1.87930f, 1.0f), + Point4D(-2.00000f, 0.68410f, -1.87930f, 1.0f), + Point4D(-2.00000f, -0.68400f, 1.87940f, 1.0f)}, + MozPolygon{Point4D(-0.00000f, 1.19540f, 2.56350f, 1.0f), + Point4D(-0.00000f, -2.56340f, 1.19540f, 1.0f), + Point4D(0.00010f, -1.19530f, -2.56340f, 1.0f), + Point4D(0.00010f, 2.56350f, -1.19530f, 1.0f)}}; + ::RunTest(polygons, expected); +} + +TEST(BSPTree, TwoPlaneIntersectRotate220degrees) +{ + const std::deque<MozPolygon> polygons{ + MozPolygon{Point4D(-0.00000f, 0.73210f, -2.73200f, 1.0f), + Point4D(-0.00000f, 2.73210f, 0.73210f, 1.0f), + Point4D(0.00010f, -0.73200f, 2.73210f, 1.0f), + Point4D(0.00010f, -2.73200f, -0.73200f, 1.0f)}, + MozPolygon{Point4D(2.00000f, 1.73210f, -0.99990f, 1.0f), + Point4D(2.00000f, -1.73200f, 1.00000f, 1.0f), + Point4D(-2.00000f, -1.73200f, 1.00000f, 1.0f), + Point4D(-2.00000f, 1.73210f, -0.99990f, 1.0f)}}; + + const std::deque<MozPolygon> expected{ + MozPolygon{Point4D(-0.00000f, 0.73210f, -2.73200f, 1.0f), + Point4D(-0.00000f, 2.73210f, 0.73210f, 1.0f), + Point4D(0.00010f, -0.73200f, 2.73210f, 1.0f), + Point4D(0.00010f, -2.73200f, -0.73200f, 1.0f)}, + MozPolygon{Point4D(2.00000f, 1.73210f, -0.99990f, 1.0f), + Point4D(2.00000f, -1.73200f, 1.00000f, 1.0f), + Point4D(-2.00000f, -1.73200f, 1.00000f, 1.0f), + Point4D(-2.00000f, 1.73210f, -0.99990f, 1.0f)}}; + ::RunTest(polygons, expected); +} + +TEST(BSPTree, TwoPlaneIntersectRotate240degrees) +{ + const std::deque<MozPolygon> polygons{ + MozPolygon{Point4D(-0.00000f, -2.73200f, 0.73210f, 1.0f), + Point4D(-0.00000f, -0.73200f, -2.73200f, 1.0f), + Point4D(0.00010f, 2.73210f, -0.73200f, 1.0f), + Point4D(0.00010f, 0.73210f, 2.73210f, 1.0f)}, + MozPolygon{Point4D(2.00000f, -1.73200f, -1.00000f, 1.0f), + Point4D(2.00000f, 1.73210f, 1.00010f, 1.0f), + Point4D(-2.00000f, 1.73210f, 1.00010f, 1.0f), + Point4D(-2.00000f, -1.73200f, -1.00000f, 1.0f)}}; + + const std::deque<MozPolygon> expected{ + MozPolygon{Point4D(-2.00000f, 1.26793f, 0.73210f, 1.0f), + Point4D(-2.00000f, -1.73200f, -1.00000f, 1.0f), + Point4D(2.00000f, -1.73200f, -1.00000f, 1.0f), + Point4D(2.00000f, 1.26793f, 0.73210f, 1.0f)}, + MozPolygon{Point4D(-0.00000f, -2.73200f, 0.73210f, 1.0f), + Point4D(-0.00000f, -0.73200f, -2.73200f, 1.0f), + Point4D(0.00010f, 2.73210f, -0.73200f, 1.0f), + Point4D(0.00010f, 0.73210f, 2.73210f, 1.0f)}, + MozPolygon{Point4D(2.00000f, 1.26793f, 0.73210f, 1.0f), + Point4D(2.00000f, 1.73210f, 1.00010f, 1.0f), + Point4D(-2.00000f, 1.73210f, 1.00010f, 1.0f), + Point4D(-2.00000f, 1.26793f, 0.73210f, 1.0f)}}; + ::RunTest(polygons, expected); +} + +TEST(BSPTree, TwoPlaneIntersectRotate260degrees) +{ + const std::deque<MozPolygon> polygons{ + MozPolygon{Point4D(-0.00000f, 1.19540f, 2.56350f, 1.0f), + Point4D(-0.00000f, -2.56340f, 1.19540f, 1.0f), + Point4D(0.00010f, -1.19530f, -2.56340f, 1.0f), + Point4D(0.00010f, 2.56350f, -1.19530f, 1.0f)}, + MozPolygon{Point4D(2.00000f, -0.68400f, 1.87940f, 1.0f), + Point4D(2.00000f, 0.68410f, -1.87930f, 1.0f), + Point4D(-2.00000f, 0.68410f, -1.87930f, 1.0f), + Point4D(-2.00000f, -0.68400f, 1.87940f, 1.0f)}}; + + const std::deque<MozPolygon> expected{ + MozPolygon{Point4D(2.00000f, -0.68400f, 1.87940f, 1.0f), + Point4D(2.00000f, 0.68410f, -1.87930f, 1.0f), + Point4D(-2.00000f, 0.68410f, -1.87930f, 1.0f), + Point4D(-2.00000f, -0.68400f, 1.87940f, 1.0f)}, + MozPolygon{Point4D(-0.00000f, 1.19540f, 2.56350f, 1.0f), + Point4D(-0.00000f, -2.56340f, 1.19540f, 1.0f), + Point4D(0.00010f, -1.19530f, -2.56340f, 1.0f), + Point4D(0.00010f, 2.56350f, -1.19530f, 1.0f)}}; + ::RunTest(polygons, expected); +} + +TEST(BSPTree, TwoPlaneIntersectRotate280degrees) +{ + const std::deque<MozPolygon> polygons{ + MozPolygon{Point4D(-0.00000f, 2.73210f, -0.73200f, 1.0f), + Point4D(-0.00000f, 0.73210f, 2.73210f, 1.0f), + Point4D(0.00010f, -2.73200f, 0.73210f, 1.0f), + Point4D(0.00010f, -0.73200f, -2.73200f, 1.0f)}, + MozPolygon{Point4D(2.00000f, 1.73210f, 1.00010f, 1.0f), + Point4D(2.00000f, -1.73200f, -1.00000f, 1.0f), + Point4D(-2.00000f, -1.73200f, -1.00000f, 1.0f), + Point4D(-2.00000f, 1.73210f, 1.00010f, 1.0f)}}; + + const std::deque<MozPolygon> expected{ + MozPolygon{Point4D(2.00000f, -1.26783f, -0.73200f, 1.0f), + Point4D(2.00000f, -1.73200f, -1.00000f, 1.0f), + Point4D(-2.00000f, -1.73200f, -1.00000f, 1.0f), + Point4D(-2.00000f, -1.26783f, -0.73200f, 1.0f)}, + MozPolygon{Point4D(-0.00000f, 2.73210f, -0.73200f, 1.0f), + Point4D(-0.00000f, 0.73210f, 2.73210f, 1.0f), + Point4D(0.00010f, -2.73200f, 0.73210f, 1.0f), + Point4D(0.00010f, -0.73200f, -2.73200f, 1.0f)}, + MozPolygon{Point4D(-2.00000f, -1.26783f, -0.73200f, 1.0f), + Point4D(-2.00000f, 1.73210f, 1.00010f, 1.0f), + Point4D(2.00000f, 1.73210f, 1.00010f, 1.0f), + Point4D(2.00000f, -1.26783f, -0.73200f, 1.0f)}}; + ::RunTest(polygons, expected); +} + +TEST(BSPTree, TwoPlaneIntersectRotate300degrees) +{ + const std::deque<MozPolygon> polygons{ + MozPolygon{Point4D(-0.00000f, 0.73210f, -2.73200f, 1.0f), + Point4D(-0.00000f, 2.73210f, 0.73210f, 1.0f), + Point4D(0.00010f, -0.73200f, 2.73210f, 1.0f), + Point4D(0.00010f, -2.73200f, -0.73200f, 1.0f)}, + MozPolygon{Point4D(2.00000f, 1.73210f, -0.99990f, 1.0f), + Point4D(2.00000f, -1.73200f, 1.00000f, 1.0f), + Point4D(-2.00000f, -1.73200f, 1.00000f, 1.0f), + Point4D(-2.00000f, 1.73210f, -0.99990f, 1.0f)}}; + + const std::deque<MozPolygon> expected{ + MozPolygon{Point4D(-0.00000f, 0.73210f, -2.73200f, 1.0f), + Point4D(-0.00000f, 2.73210f, 0.73210f, 1.0f), + Point4D(0.00010f, -0.73200f, 2.73210f, 1.0f), + Point4D(0.00010f, -2.73200f, -0.73200f, 1.0f)}, + MozPolygon{Point4D(2.00000f, 1.73210f, -0.99990f, 1.0f), + Point4D(2.00000f, -1.73200f, 1.00000f, 1.0f), + Point4D(-2.00000f, -1.73200f, 1.00000f, 1.0f), + Point4D(-2.00000f, 1.73210f, -0.99990f, 1.0f)}}; + ::RunTest(polygons, expected); +} + +TEST(BSPTree, TwoPlaneIntersectRotate320degrees) +{ + const std::deque<MozPolygon> polygons{ + MozPolygon{Point4D(-0.00000f, -1.19530f, -2.56340f, 1.0f), + Point4D(-0.00000f, 2.56350f, -1.19530f, 1.0f), + Point4D(0.00010f, 1.19540f, 2.56350f, 1.0f), + Point4D(0.00010f, -2.56340f, 1.19540f, 1.0f)}, + MozPolygon{Point4D(2.00000f, 0.68410f, -1.87930f, 1.0f), + Point4D(2.00000f, -0.68400f, 1.87940f, 1.0f), + Point4D(-2.00000f, -0.68400f, 1.87940f, 1.0f), + Point4D(-2.00000f, 0.68410f, -1.87930f, 1.0f)}}; + + const std::deque<MozPolygon> expected{ + MozPolygon{Point4D(-0.00000f, -1.19530f, -2.56340f, 1.0f), + Point4D(-0.00000f, 2.56350f, -1.19530f, 1.0f), + Point4D(0.00010f, 1.19540f, 2.56350f, 1.0f), + Point4D(0.00010f, -2.56340f, 1.19540f, 1.0f)}, + MozPolygon{Point4D(2.00000f, 0.68410f, -1.87930f, 1.0f), + Point4D(2.00000f, -0.68400f, 1.87940f, 1.0f), + Point4D(-2.00000f, -0.68400f, 1.87940f, 1.0f), + Point4D(-2.00000f, 0.68410f, -1.87930f, 1.0f)}}; + ::RunTest(polygons, expected); +} + +TEST(BSPTree, TwoPlaneIntersectRotate340degrees) +{ + const std::deque<MozPolygon> polygons{ + MozPolygon{Point4D(-0.00000f, -2.00000f, -2.00000f, 1.0f), + Point4D(-0.00000f, 2.00000f, -2.00000f, 1.0f), + Point4D(0.00010f, 2.00000f, 2.00000f, 1.0f), + Point4D(0.00010f, -2.00000f, 2.00000f, 1.0f)}, + MozPolygon{Point4D(2.00000f, 0.00010f, -2.00000f, 1.0f), + Point4D(2.00000f, -0.00000f, 2.00000f, 1.0f), + Point4D(-2.00000f, -0.00000f, 2.00000f, 1.0f), + Point4D(-2.00000f, 0.00010f, -2.00000f, 1.0f)}}; + + const std::deque<MozPolygon> expected{ + MozPolygon{Point4D(-0.00000f, -2.00000f, -2.00000f, 1.0f), + Point4D(-0.00000f, 2.00000f, -2.00000f, 1.0f), + Point4D(0.00010f, 2.00000f, 2.00000f, 1.0f), + Point4D(0.00010f, -2.00000f, 2.00000f, 1.0f)}, + MozPolygon{Point4D(2.00000f, 0.00010f, -2.00000f, 1.0f), + Point4D(2.00000f, -0.00000f, 2.00000f, 1.0f), + Point4D(-2.00000f, -0.00000f, 2.00000f, 1.0f), + Point4D(-2.00000f, 0.00010f, -2.00000f, 1.0f)}}; + ::RunTest(polygons, expected); +} + +TEST(BSPTree, TwoPlaneIntersectRotate360degrees) +{ + const std::deque<MozPolygon> polygons{ + MozPolygon{Point4D(-0.00000f, -2.00000f, -2.00000f, 1.0f), + Point4D(-0.00000f, 2.00000f, -2.00000f, 1.0f), + Point4D(0.00010f, 2.00000f, 2.00000f, 1.0f), + Point4D(0.00010f, -2.00000f, 2.00000f, 1.0f)}, + MozPolygon{Point4D(2.00000f, 0.00010f, -2.00000f, 1.0f), + Point4D(2.00000f, -0.00000f, 2.00000f, 1.0f), + Point4D(-2.00000f, -0.00000f, 2.00000f, 1.0f), + Point4D(-2.00000f, 0.00010f, -2.00000f, 1.0f)}}; + + const std::deque<MozPolygon> expected{ + MozPolygon{Point4D(-0.00000f, -2.00000f, -2.00000f, 1.0f), + Point4D(-0.00000f, 2.00000f, -2.00000f, 1.0f), + Point4D(0.00010f, 2.00000f, 2.00000f, 1.0f), + Point4D(0.00010f, -2.00000f, 2.00000f, 1.0f)}, + MozPolygon{Point4D(2.00000f, 0.00010f, -2.00000f, 1.0f), + Point4D(2.00000f, -0.00000f, 2.00000f, 1.0f), + Point4D(-2.00000f, -0.00000f, 2.00000f, 1.0f), + Point4D(-2.00000f, 0.00010f, -2.00000f, 1.0f)}}; + ::RunTest(polygons, expected); +} diff --git a/gfx/tests/gtest/TestBufferRotation.cpp b/gfx/tests/gtest/TestBufferRotation.cpp new file mode 100644 index 0000000000..871295e68c --- /dev/null +++ b/gfx/tests/gtest/TestBufferRotation.cpp @@ -0,0 +1,162 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "gtest/gtest.h" + +#include "BufferUnrotate.h" + +using mozilla::gfx::BufferUnrotate; + +static unsigned char* GenerateBuffer(int bytesPerPixel, int width, int height, + int stride, int xBoundary, int yBoundary) { + unsigned char* buffer = new unsigned char[stride * height]; + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + int pos = ((yBoundary + y) % height) * stride + + ((xBoundary + x) % width) * bytesPerPixel; + for (int i = 0; i < bytesPerPixel; i++) { + buffer[pos + i] = (x + y + i * 2) % 256; + } + } + } + return buffer; +} + +static bool CheckBuffer(unsigned char* buffer, int bytesPerPixel, int width, + int height, int stride) { + int xBoundary = 0; + int yBoundary = 0; + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + int pos = ((yBoundary + y) % height) * stride + + ((xBoundary + x) % width) * bytesPerPixel; + for (int i = 0; i < bytesPerPixel; i++) { + if (buffer[pos + i] != (x + y + i * 2) % 256) { + printf("Buffer differs at %i, %i, is %i\n", x, y, + (int)buffer[pos + i]); + return false; + } + } + } + } + return true; +} + +TEST(Gfx, BufferUnrotateHorizontal) +{ + const int NUM_OF_TESTS = 8; + int bytesPerPixelList[2] = {2, 4}; + int width[NUM_OF_TESTS] = {100, 100, 99, 99, 100, 100, 99, 99}; + int height[NUM_OF_TESTS] = {100, 99, 100, 99, 100, 99, 100, 99}; + int xBoundary[NUM_OF_TESTS] = {30, 30, 30, 30, 31, 31, 31, 31}; + int yBoundary[NUM_OF_TESTS] = {0, 0, 0, 0}; + + for (int bytesPerId = 0; bytesPerId < 2; bytesPerId++) { + int bytesPerPixel = bytesPerPixelList[bytesPerId]; + int stride = 256 * bytesPerPixel; + for (int testId = 0; testId < NUM_OF_TESTS; testId++) { + unsigned char* buffer = + GenerateBuffer(bytesPerPixel, width[testId], height[testId], stride, + xBoundary[testId], yBoundary[testId]); + BufferUnrotate(buffer, width[testId] * bytesPerPixel, height[testId], + stride, xBoundary[testId] * bytesPerPixel, + yBoundary[testId]); + + EXPECT_TRUE(CheckBuffer(buffer, bytesPerPixel, width[testId], + height[testId], stride)); + delete[] buffer; + } + } +} + +TEST(Gfx, BufferUnrotateVertical) +{ + const int NUM_OF_TESTS = 8; + int bytesPerPixelList[2] = {2, 4}; + int width[NUM_OF_TESTS] = {100, 100, 99, 99, 100, 100, 99, 99}; + int height[NUM_OF_TESTS] = {100, 99, 100, 99, 100, 99, 100, 99}; + int xBoundary[NUM_OF_TESTS] = {0, 0, 0, 0}; + int yBoundary[NUM_OF_TESTS] = {30, 30, 30, 30, 31, 31, 31, 31}; + + for (int bytesPerId = 0; bytesPerId < 2; bytesPerId++) { + int bytesPerPixel = bytesPerPixelList[bytesPerId]; + int stride = 256 * bytesPerPixel; + for (int testId = 0; testId < NUM_OF_TESTS; testId++) { + unsigned char* buffer = + GenerateBuffer(bytesPerPixel, width[testId], height[testId], stride, + xBoundary[testId], yBoundary[testId]); + BufferUnrotate(buffer, width[testId] * bytesPerPixel, height[testId], + stride, xBoundary[testId] * bytesPerPixel, + yBoundary[testId]); + + EXPECT_TRUE(CheckBuffer(buffer, bytesPerPixel, width[testId], + height[testId], stride)); + delete[] buffer; + } + } +} + +TEST(Gfx, BufferUnrotateBoth) +{ + const int NUM_OF_TESTS = 16; + int bytesPerPixelList[2] = {2, 4}; + int width[NUM_OF_TESTS] = {100, 100, 99, 99, 100, 100, 99, 99, + 100, 100, 99, 99, 100, 100, 99, 99}; + int height[NUM_OF_TESTS] = {100, 99, 100, 99, 100, 99, 100, 99, + 100, 99, 100, 99, 100, 99, 100, 99}; + int xBoundary[NUM_OF_TESTS] = {30, 30, 30, 30, 31, 31, 31, 31, + 30, 30, 30, 30, 31, 31, 31, 31}; + int yBoundary[NUM_OF_TESTS] = {30, 30, 30, 30, 30, 30, 30, 30, + 31, 31, 31, 31, 31, 31, 31, 31}; + + for (int bytesPerId = 0; bytesPerId < 2; bytesPerId++) { + int bytesPerPixel = bytesPerPixelList[bytesPerId]; + int stride = 256 * bytesPerPixel; + for (int testId = 0; testId < NUM_OF_TESTS; testId++) { + unsigned char* buffer = + GenerateBuffer(bytesPerPixel, width[testId], height[testId], stride, + xBoundary[testId], yBoundary[testId]); + BufferUnrotate(buffer, width[testId] * bytesPerPixel, height[testId], + stride, xBoundary[testId] * bytesPerPixel, + yBoundary[testId]); + + EXPECT_TRUE(CheckBuffer(buffer, bytesPerPixel, width[testId], + height[testId], stride)); + delete[] buffer; + } + } +} + +TEST(Gfx, BufferUnrotateUneven) +{ + const int NUM_OF_TESTS = 16; + int bytesPerPixelList[2] = {2, 4}; + int width[NUM_OF_TESTS] = {10, 100, 99, 39, 100, 40, 99, 39, + 100, 50, 39, 99, 74, 60, 99, 39}; + int height[NUM_OF_TESTS] = {100, 39, 10, 99, 10, 99, 40, 99, + 73, 39, 100, 39, 67, 99, 84, 99}; + int xBoundary[NUM_OF_TESTS] = {0, 0, 30, 30, 99, 31, 0, 31, + 30, 30, 30, 30, 31, 31, 31, 38}; + int yBoundary[NUM_OF_TESTS] = {30, 30, 0, 30, 0, 30, 0, 30, + 31, 31, 31, 31, 31, 31, 31, 98}; + + for (int bytesPerId = 0; bytesPerId < 2; bytesPerId++) { + int bytesPerPixel = bytesPerPixelList[bytesPerId]; + int stride = 256 * bytesPerPixel; + for (int testId = 0; testId < NUM_OF_TESTS; testId++) { + unsigned char* buffer = + GenerateBuffer(bytesPerPixel, width[testId], height[testId], stride, + xBoundary[testId], yBoundary[testId]); + BufferUnrotate(buffer, width[testId] * bytesPerPixel, height[testId], + stride, xBoundary[testId] * bytesPerPixel, + yBoundary[testId]); + + EXPECT_TRUE(CheckBuffer(buffer, bytesPerPixel, width[testId], + height[testId], stride)); + delete[] buffer; + } + } +} diff --git a/gfx/tests/gtest/TestConfigManager.cpp b/gfx/tests/gtest/TestConfigManager.cpp new file mode 100644 index 0000000000..d76dcfd234 --- /dev/null +++ b/gfx/tests/gtest/TestConfigManager.cpp @@ -0,0 +1,890 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "gtest/gtest.h" + +#include "gfxFeature.h" +#include "mozilla/gfx/gfxConfigManager.h" +#include "nsIGfxInfo.h" + +using namespace mozilla; +using namespace mozilla::gfx; + +namespace mozilla { +namespace gfx { + +class MockGfxInfo final : public nsIGfxInfo { + public: + NS_DECL_THREADSAFE_ISUPPORTS + + int32_t mStatusWr; + int32_t mStatusWrCompositor; + int32_t mStatusWrShaderCache; + int32_t mStatusWrOptimizedShaders; + int32_t mStatusWrPartialPresent; + int32_t mStatusWrScissoredCacheClears; + int32_t mMaxRefreshRate; + bool mHasMixedRefreshRate; + Maybe<bool> mHasBattery; + const char* mVendorId; + const char* mDeviceId; + + // Default allows WebRender + compositor, and is desktop NVIDIA. + MockGfxInfo() + : mStatusWr(nsIGfxInfo::FEATURE_STATUS_OK), + mStatusWrCompositor(nsIGfxInfo::FEATURE_STATUS_OK), + mStatusWrShaderCache(nsIGfxInfo::FEATURE_STATUS_OK), + mStatusWrOptimizedShaders(nsIGfxInfo::FEATURE_STATUS_OK), + mStatusWrPartialPresent(nsIGfxInfo::FEATURE_STATUS_OK), + mStatusWrScissoredCacheClears(nsIGfxInfo::FEATURE_STATUS_OK), + mMaxRefreshRate(-1), + mHasMixedRefreshRate(false), + mHasBattery(Some(false)), + mVendorId("0x10de"), + mDeviceId("") {} + + NS_IMETHOD GetFeatureStatus(int32_t aFeature, nsACString& aFailureId, + int32_t* _retval) override { + switch (aFeature) { + case nsIGfxInfo::FEATURE_WEBRENDER: + *_retval = mStatusWr; + break; + case nsIGfxInfo::FEATURE_WEBRENDER_COMPOSITOR: + *_retval = mStatusWrCompositor; + break; + case nsIGfxInfo::FEATURE_WEBRENDER_SHADER_CACHE: + *_retval = mStatusWrShaderCache; + break; + case nsIGfxInfo::FEATURE_WEBRENDER_OPTIMIZED_SHADERS: + *_retval = mStatusWrOptimizedShaders; + break; + case nsIGfxInfo::FEATURE_WEBRENDER_PARTIAL_PRESENT: + *_retval = mStatusWrPartialPresent; + break; + case nsIGfxInfo::FEATURE_WEBRENDER_SCISSORED_CACHE_CLEARS: + *_retval = mStatusWrScissoredCacheClears; + break; + default: + return NS_ERROR_NOT_IMPLEMENTED; + } + return NS_OK; + } + + NS_IMETHOD GetHasBattery(bool* aHasBattery) override { + if (mHasBattery.isNothing()) { + return NS_ERROR_NOT_IMPLEMENTED; + } + *aHasBattery = *mHasBattery; + return NS_OK; + } + + NS_IMETHOD GetAdapterVendorID(nsAString& aAdapterVendorID) override { + if (!mVendorId) { + return NS_ERROR_NOT_IMPLEMENTED; + } + aAdapterVendorID.AssignASCII(mVendorId); + return NS_OK; + } + + NS_IMETHOD GetAdapterDeviceID(nsAString& aAdapterDeviceID) override { + if (!mDeviceId) { + return NS_ERROR_NOT_IMPLEMENTED; + } + aAdapterDeviceID.AssignASCII(mDeviceId); + return NS_OK; + } + + NS_IMETHOD_(int32_t) GetMaxRefreshRate(bool* aMixed) override { + if (aMixed) { + *aMixed = mHasMixedRefreshRate; + } + return mMaxRefreshRate; + } + + NS_IMETHODIMP GetEmbeddedInFirefoxReality( + bool* aEmbeddedInFirefoxReality) override { + return NS_ERROR_NOT_IMPLEMENTED; + } + + NS_IMETHOD GetAzureCanvasBackend(nsAString& aBackend) override { + return NS_ERROR_NOT_IMPLEMENTED; + } + + NS_IMETHOD GetAzureContentBackend(nsAString& aBackend) override { + return NS_ERROR_NOT_IMPLEMENTED; + } + + // The following methods we don't need for testing gfxConfigManager. + NS_IMETHOD GetFeatureSuggestedDriverVersion(int32_t aFeature, + nsAString& _retval) override { + return NS_ERROR_NOT_IMPLEMENTED; + } + + NS_IMETHOD GetMonitors(JSContext* cx, + JS::MutableHandle<JS::Value> _retval) override { + return NS_ERROR_NOT_IMPLEMENTED; + } + NS_IMETHOD GetFailures(nsTArray<int32_t>& indices, + nsTArray<nsCString>& failures) override { + return NS_ERROR_NOT_IMPLEMENTED; + } + NS_IMETHOD_(void) LogFailure(const nsACString& failure) override {} + NS_IMETHOD GetInfo(JSContext*, JS::MutableHandle<JS::Value>) override { + return NS_ERROR_NOT_IMPLEMENTED; + } + NS_IMETHOD GetFeatures(JSContext*, JS::MutableHandle<JS::Value>) override { + return NS_ERROR_NOT_IMPLEMENTED; + } + NS_IMETHOD GetFeatureLog(JSContext*, JS::MutableHandle<JS::Value>) override { + return NS_ERROR_NOT_IMPLEMENTED; + } + NS_IMETHOD GetActiveCrashGuards(JSContext*, + JS::MutableHandle<JS::Value>) override { + return NS_ERROR_NOT_IMPLEMENTED; + } + NS_IMETHOD GetFontVisibilityDetermination( + nsIGfxInfo::FontVisibilityDeviceDetermination* + aFontVisibilityDetermination) override { + return NS_ERROR_NOT_IMPLEMENTED; + } + NS_IMETHOD GetFontVisibilityDeterminationStr( + nsAString& aFontVisibilityDeterminationStr) override { + return NS_ERROR_NOT_IMPLEMENTED; + } + NS_IMETHOD GetContentBackend(nsAString& aContentBackend) override { + return NS_ERROR_NOT_IMPLEMENTED; + } + NS_IMETHOD GetUsingGPUProcess(bool* aOutValue) override { + return NS_ERROR_NOT_IMPLEMENTED; + } + NS_IMETHOD GetIsHeadless(bool* aIsHeadless) override { + return NS_ERROR_NOT_IMPLEMENTED; + } + NS_IMETHOD GetTargetFrameRate(uint32_t* aTargetFrameRate) override { + return NS_ERROR_NOT_IMPLEMENTED; + } + NS_IMETHOD GetCodecSupportInfo(nsACString& aCodecSupportInfo) override { + return NS_ERROR_NOT_IMPLEMENTED; + } + NS_IMETHOD GetD2DEnabled(bool* aD2DEnabled) override { + return NS_ERROR_NOT_IMPLEMENTED; + } + NS_IMETHOD GetDWriteEnabled(bool* aDWriteEnabled) override { + return NS_ERROR_NOT_IMPLEMENTED; + } + NS_IMETHOD GetDWriteVersion(nsAString& aDwriteVersion) override { + return NS_ERROR_NOT_IMPLEMENTED; + } + NS_IMETHOD GetCleartypeParameters(nsAString& aCleartypeParams) override { + return NS_ERROR_NOT_IMPLEMENTED; + } + NS_IMETHOD GetWindowProtocol(nsAString& aWindowProtocol) override { + return NS_ERROR_NOT_IMPLEMENTED; + } + NS_IMETHOD GetTestType(nsAString& aTestType) override { + return NS_ERROR_NOT_IMPLEMENTED; + } + NS_IMETHOD GetAdapterDescription(nsAString& aAdapterDescription) override { + return NS_ERROR_NOT_IMPLEMENTED; + } + NS_IMETHOD GetAdapterDriver(nsAString& aAdapterDriver) override { + return NS_ERROR_NOT_IMPLEMENTED; + } + NS_IMETHOD GetAdapterSubsysID(nsAString& aAdapterSubsysID) override { + return NS_ERROR_NOT_IMPLEMENTED; + } + NS_IMETHOD GetAdapterRAM(uint32_t* aAdapterRAM) override { + return NS_ERROR_NOT_IMPLEMENTED; + } + NS_IMETHOD GetAdapterDriverVendor(nsAString& aAdapterDriverVendor) override { + return NS_ERROR_NOT_IMPLEMENTED; + } + NS_IMETHOD GetAdapterDriverVersion( + nsAString& aAdapterDriverVersion) override { + return NS_ERROR_NOT_IMPLEMENTED; + } + NS_IMETHOD GetAdapterDriverDate(nsAString& aAdapterDriverDate) override { + return NS_ERROR_NOT_IMPLEMENTED; + } + NS_IMETHOD GetAdapterDescription2(nsAString& aAdapterDescription) override { + return NS_ERROR_NOT_IMPLEMENTED; + } + NS_IMETHOD GetAdapterDriver2(nsAString& aAdapterDriver) override { + return NS_ERROR_NOT_IMPLEMENTED; + } + NS_IMETHOD GetAdapterVendorID2(nsAString& aAdapterVendorID) override { + return NS_ERROR_NOT_IMPLEMENTED; + } + NS_IMETHOD GetAdapterDeviceID2(nsAString& aAdapterDeviceID) override { + return NS_ERROR_NOT_IMPLEMENTED; + } + NS_IMETHOD GetAdapterSubsysID2(nsAString& aAdapterSubsysID) override { + return NS_ERROR_NOT_IMPLEMENTED; + } + NS_IMETHOD GetAdapterRAM2(uint32_t* aAdapterRAM) override { + return NS_ERROR_NOT_IMPLEMENTED; + } + NS_IMETHOD GetAdapterDriverVendor2(nsAString& aAdapterDriverVendor) override { + return NS_ERROR_NOT_IMPLEMENTED; + } + NS_IMETHOD GetAdapterDriverVersion2( + nsAString& aAdapterDriverVersion) override { + return NS_ERROR_NOT_IMPLEMENTED; + } + NS_IMETHOD GetAdapterDriverDate2(nsAString& aAdapterDriverDate) override { + return NS_ERROR_NOT_IMPLEMENTED; + } + NS_IMETHOD GetIsGPU2Active(bool* aIsGPU2Active) override { + return NS_ERROR_NOT_IMPLEMENTED; + } + NS_IMETHOD GetDrmRenderDevice(nsACString& aDrmRenderDevice) override { + return NS_ERROR_NOT_IMPLEMENTED; + } + NS_IMETHOD ControlGPUProcessForXPCShell(bool aEnable, + bool* _retval) override { + return NS_ERROR_NOT_IMPLEMENTED; + } + NS_IMETHOD KillGPUProcessForTests() override { + return NS_ERROR_NOT_IMPLEMENTED; + } + NS_IMETHOD CrashGPUProcessForTests() override { + return NS_ERROR_NOT_IMPLEMENTED; + } + + NS_IMETHOD_(void) GetData() override {} + + private: + virtual ~MockGfxInfo() = default; +}; + +NS_IMPL_ISUPPORTS(MockGfxInfo, nsIGfxInfo) + +class GfxConfigManager : public ::testing::Test, public gfxConfigManager { + public: + GfxConfigManager() { + mFeatureWr = &mFeatures.mWr; + mFeatureWrCompositor = &mFeatures.mWrCompositor; + mFeatureWrAngle = &mFeatures.mWrAngle; + mFeatureWrDComp = &mFeatures.mWrDComp; + mFeatureWrPartial = &mFeatures.mWrPartial; + mFeatureWrShaderCache = &mFeatures.mWrShaderCache; + mFeatureWrOptimizedShaders = &mFeatures.mWrOptimizedShaders; + mFeatureWrScissoredCacheClears = &mFeatures.mWrScissoredCacheClears; + mFeatureHwCompositing = &mFeatures.mHwCompositing; + mFeatureGPUProcess = &mFeatures.mGPUProcess; + } + + void SetUp() override { + mMockGfxInfo = new MockGfxInfo(); + mGfxInfo = mMockGfxInfo; + + mFeatureD3D11HwAngle = &mFeatures.mD3D11HwAngle; + mFeatureD3D11Compositing = &mFeatures.mD3D11Compositing; + + // By default, turn everything on. This effectively assumes we are on + // qualified nightly Windows 10 with DirectComposition support. + mFeatureHwCompositing->EnableByDefault(); + mFeatureD3D11HwAngle->EnableByDefault(); + mFeatureD3D11Compositing->EnableByDefault(); + mFeatureGPUProcess->EnableByDefault(); + + mWrCompositorEnabled.emplace(true); + mWrPartialPresent = true; + mWrShaderCache.emplace(true); + mWrOptimizedShaders = true; + mWrForceAngle = true; + mWrDCompWinEnabled = true; + mWrCompositorDCompRequired = true; + mWrScissoredCacheClearsEnabled = true; + ++mHwStretchingSupport.mBoth; + mIsWin11OrLater = true; + mIsNightly = true; + mIsEarlyBetaOrEarlier = true; + } + + void TearDown() override { + mFeatures.mWr.Reset(); + mFeatures.mWrCompositor.Reset(); + mFeatures.mWrAngle.Reset(); + mFeatures.mWrDComp.Reset(); + mFeatures.mWrPartial.Reset(); + mFeatures.mWrShaderCache.Reset(); + mFeatures.mWrOptimizedShaders.Reset(); + mFeatures.mWrScissoredCacheClears.Reset(); + mFeatures.mHwCompositing.Reset(); + mFeatures.mD3D11HwAngle.Reset(); + mFeatures.mD3D11Compositing.Reset(); + mFeatures.mGPUProcess.Reset(); + } + + struct { + FeatureState mWr; + FeatureState mWrCompositor; + FeatureState mWrAngle; + FeatureState mWrDComp; + FeatureState mWrPartial; + FeatureState mWrShaderCache; + FeatureState mWrOptimizedShaders; + FeatureState mWrScissoredCacheClears; + FeatureState mHwCompositing; + FeatureState mD3D11HwAngle; + FeatureState mD3D11Compositing; + FeatureState mGPUProcess; + } mFeatures; + + RefPtr<MockGfxInfo> mMockGfxInfo; +}; + +} // namespace gfx +} // namespace mozilla + +TEST_F(GfxConfigManager, WebRenderDefault) { + ConfigureWebRender(); + + EXPECT_TRUE(mFeatures.mWr.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrCompositor.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrAngle.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrDComp.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrPartial.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrShaderCache.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrOptimizedShaders.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrScissoredCacheClears.IsEnabled()); + EXPECT_TRUE(mFeatures.mHwCompositing.IsEnabled()); + EXPECT_TRUE(mFeatures.mGPUProcess.IsEnabled()); + EXPECT_TRUE(mFeatures.mD3D11HwAngle.IsEnabled()); +} + +TEST_F(GfxConfigManager, WebRenderDefaultRelease) { + mIsNightly = mIsEarlyBetaOrEarlier = false; + ConfigureWebRender(); + + EXPECT_TRUE(mFeatures.mWr.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrCompositor.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrAngle.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrDComp.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrPartial.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrShaderCache.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrOptimizedShaders.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrScissoredCacheClears.IsEnabled()); + EXPECT_TRUE(mFeatures.mHwCompositing.IsEnabled()); + EXPECT_TRUE(mFeatures.mGPUProcess.IsEnabled()); + EXPECT_TRUE(mFeatures.mD3D11HwAngle.IsEnabled()); +} + +TEST_F(GfxConfigManager, WebRenderNoPartialPresent) { + mWrPartialPresent = false; + ConfigureWebRender(); + + EXPECT_TRUE(mFeatures.mWr.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrCompositor.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrAngle.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrDComp.IsEnabled()); + EXPECT_FALSE(mFeatures.mWrPartial.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrShaderCache.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrOptimizedShaders.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrScissoredCacheClears.IsEnabled()); + EXPECT_TRUE(mFeatures.mHwCompositing.IsEnabled()); + EXPECT_TRUE(mFeatures.mGPUProcess.IsEnabled()); + EXPECT_TRUE(mFeatures.mD3D11HwAngle.IsEnabled()); +} + +TEST_F(GfxConfigManager, WebRenderPartialBlocked) { + mWrPartialPresent = true; + mMockGfxInfo->mStatusWrPartialPresent = nsIGfxInfo::FEATURE_BLOCKED_DEVICE; + ConfigureWebRender(); + + EXPECT_FALSE(mFeatures.mWrPartial.IsEnabled()); +} + +TEST_F(GfxConfigManager, WebRenderForcePartialBlocked) { + mWrForcePartialPresent = true; + mMockGfxInfo->mStatusWrPartialPresent = nsIGfxInfo::FEATURE_BLOCKED_DEVICE; + ConfigureWebRender(); + + EXPECT_TRUE(mFeatures.mWrPartial.IsEnabled()); +} + +TEST_F(GfxConfigManager, WebRenderScaledResolutionWithHwStretching) { + mScaledResolution = true; + ConfigureWebRender(); + + EXPECT_TRUE(mFeatures.mWr.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrCompositor.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrAngle.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrDComp.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrShaderCache.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrOptimizedShaders.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrScissoredCacheClears.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrPartial.IsEnabled()); + EXPECT_TRUE(mFeatures.mHwCompositing.IsEnabled()); + EXPECT_TRUE(mFeatures.mGPUProcess.IsEnabled()); + EXPECT_TRUE(mFeatures.mD3D11HwAngle.IsEnabled()); +} + +TEST_F(GfxConfigManager, WebRenderScaledResolutionNoHwStretching) { + ++mHwStretchingSupport.mNone; + mScaledResolution = true; + ConfigureWebRender(); + + EXPECT_TRUE(mFeatures.mWr.IsEnabled()); + EXPECT_FALSE(mFeatures.mWrCompositor.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrAngle.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrDComp.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrShaderCache.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrOptimizedShaders.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrScissoredCacheClears.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrPartial.IsEnabled()); + EXPECT_TRUE(mFeatures.mHwCompositing.IsEnabled()); + EXPECT_TRUE(mFeatures.mGPUProcess.IsEnabled()); + EXPECT_TRUE(mFeatures.mD3D11HwAngle.IsEnabled()); +} + +TEST_F(GfxConfigManager, WebRenderEnabledWithDisableHwCompositingNoWr) { + mDisableHwCompositingNoWr = true; + ConfigureWebRender(); + + EXPECT_TRUE(mFeatures.mWr.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrCompositor.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrAngle.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrDComp.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrShaderCache.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrOptimizedShaders.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrScissoredCacheClears.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrPartial.IsEnabled()); + EXPECT_TRUE(mFeatures.mHwCompositing.IsEnabled()); + EXPECT_TRUE(mFeatures.mGPUProcess.IsEnabled()); + EXPECT_TRUE(mFeatures.mD3D11HwAngle.IsEnabled()); +} + +TEST_F(GfxConfigManager, WebRenderDisabledWithDisableHwCompositingNoWr) { + mDisableHwCompositingNoWr = true; + mMockGfxInfo->mStatusWr = nsIGfxInfo::FEATURE_BLOCKED_DEVICE; + ConfigureWebRender(); + + EXPECT_FALSE(mFeatures.mWr.IsEnabled()); + EXPECT_FALSE(mFeatures.mWrCompositor.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrAngle.IsEnabled()); + EXPECT_FALSE(mFeatures.mWrDComp.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrPartial.IsEnabled()); + EXPECT_FALSE(mFeatures.mWrShaderCache.IsEnabled()); + EXPECT_FALSE(mFeatures.mWrOptimizedShaders.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrScissoredCacheClears.IsEnabled()); + EXPECT_FALSE(mFeatures.mHwCompositing.IsEnabled()); + EXPECT_FALSE(mFeatures.mGPUProcess.IsEnabled()); + EXPECT_TRUE(mFeatures.mD3D11HwAngle.IsEnabled()); +} + +TEST_F(GfxConfigManager, WebRenderDisabledWithAllowSoftwareGPUProcess) { + mDisableHwCompositingNoWr = true; + mMockGfxInfo->mStatusWr = nsIGfxInfo::FEATURE_BLOCKED_DEVICE; + mGPUProcessAllowSoftware = true; + ConfigureWebRender(); + + EXPECT_FALSE(mFeatures.mWr.IsEnabled()); + EXPECT_FALSE(mFeatures.mWrCompositor.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrAngle.IsEnabled()); + EXPECT_FALSE(mFeatures.mWrDComp.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrPartial.IsEnabled()); + EXPECT_FALSE(mFeatures.mWrShaderCache.IsEnabled()); + EXPECT_FALSE(mFeatures.mWrOptimizedShaders.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrScissoredCacheClears.IsEnabled()); + EXPECT_FALSE(mFeatures.mHwCompositing.IsEnabled()); + EXPECT_TRUE(mFeatures.mGPUProcess.IsEnabled()); + EXPECT_TRUE(mFeatures.mD3D11HwAngle.IsEnabled()); +} + +TEST_F(GfxConfigManager, WebRenderSafeMode) { + mSafeMode = true; + ConfigureWebRender(); + + EXPECT_FALSE(mFeatures.mWr.IsEnabled()); + EXPECT_FALSE(mFeatures.mWrCompositor.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrAngle.IsEnabled()); + EXPECT_FALSE(mFeatures.mWrDComp.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrPartial.IsEnabled()); + EXPECT_FALSE(mFeatures.mWrShaderCache.IsEnabled()); + EXPECT_FALSE(mFeatures.mWrOptimizedShaders.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrScissoredCacheClears.IsEnabled()); + EXPECT_TRUE(mFeatures.mHwCompositing.IsEnabled()); + EXPECT_TRUE(mFeatures.mGPUProcess.IsEnabled()); + EXPECT_TRUE(mFeatures.mD3D11HwAngle.IsEnabled()); +} + +TEST_F(GfxConfigManager, WebRenderDCompDisabled) { + mWrDCompWinEnabled = false; + ConfigureWebRender(); + + EXPECT_TRUE(mFeatures.mWr.IsEnabled()); + EXPECT_FALSE(mFeatures.mWrCompositor.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrAngle.IsEnabled()); + EXPECT_FALSE(mFeatures.mWrDComp.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrPartial.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrShaderCache.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrOptimizedShaders.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrScissoredCacheClears.IsEnabled()); + EXPECT_TRUE(mFeatures.mHwCompositing.IsEnabled()); + EXPECT_TRUE(mFeatures.mGPUProcess.IsEnabled()); + EXPECT_TRUE(mFeatures.mD3D11HwAngle.IsEnabled()); +} + +TEST_F(GfxConfigManager, WebRenderDCompNotRequired) { + mWrDCompWinEnabled = false; + mWrCompositorDCompRequired = false; + ConfigureWebRender(); + + EXPECT_TRUE(mFeatures.mWr.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrCompositor.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrAngle.IsEnabled()); + EXPECT_FALSE(mFeatures.mWrDComp.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrPartial.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrShaderCache.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrOptimizedShaders.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrScissoredCacheClears.IsEnabled()); + EXPECT_TRUE(mFeatures.mHwCompositing.IsEnabled()); + EXPECT_TRUE(mFeatures.mGPUProcess.IsEnabled()); + EXPECT_TRUE(mFeatures.mD3D11HwAngle.IsEnabled()); +} + +TEST_F(GfxConfigManager, WebRenderForceAngleDisabled) { + mWrForceAngle = false; + ConfigureWebRender(); + + EXPECT_TRUE(mFeatures.mWr.IsEnabled()); + EXPECT_FALSE(mFeatures.mWrCompositor.IsEnabled()); + EXPECT_FALSE(mFeatures.mWrAngle.IsEnabled()); + EXPECT_FALSE(mFeatures.mWrDComp.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrPartial.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrShaderCache.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrOptimizedShaders.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrScissoredCacheClears.IsEnabled()); + EXPECT_TRUE(mFeatures.mHwCompositing.IsEnabled()); + EXPECT_TRUE(mFeatures.mGPUProcess.IsEnabled()); + EXPECT_TRUE(mFeatures.mD3D11HwAngle.IsEnabled()); +} + +TEST_F(GfxConfigManager, WebRenderD3D11HwAngleDisabled) { + mFeatures.mD3D11HwAngle.UserDisable("", ""_ns); + ConfigureWebRender(); + + EXPECT_FALSE(mFeatures.mWr.IsEnabled()); + EXPECT_FALSE(mFeatures.mWrCompositor.IsEnabled()); + EXPECT_FALSE(mFeatures.mWrAngle.IsEnabled()); + EXPECT_FALSE(mFeatures.mWrDComp.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrPartial.IsEnabled()); + EXPECT_FALSE(mFeatures.mWrShaderCache.IsEnabled()); + EXPECT_FALSE(mFeatures.mWrOptimizedShaders.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrScissoredCacheClears.IsEnabled()); + EXPECT_TRUE(mFeatures.mHwCompositing.IsEnabled()); + EXPECT_TRUE(mFeatures.mGPUProcess.IsEnabled()); + EXPECT_FALSE(mFeatures.mD3D11HwAngle.IsEnabled()); +} + +TEST_F(GfxConfigManager, WebRenderD3D11HwAngleAndForceAngleDisabled) { + mWrForceAngle = false; + mFeatures.mD3D11HwAngle.UserDisable("", ""_ns); + ConfigureWebRender(); + + EXPECT_TRUE(mFeatures.mWr.IsEnabled()); + EXPECT_FALSE(mFeatures.mWrCompositor.IsEnabled()); + EXPECT_FALSE(mFeatures.mWrAngle.IsEnabled()); + EXPECT_FALSE(mFeatures.mWrDComp.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrPartial.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrShaderCache.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrOptimizedShaders.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrScissoredCacheClears.IsEnabled()); + EXPECT_TRUE(mFeatures.mHwCompositing.IsEnabled()); + EXPECT_TRUE(mFeatures.mGPUProcess.IsEnabled()); + EXPECT_FALSE(mFeatures.mD3D11HwAngle.IsEnabled()); +} + +TEST_F(GfxConfigManager, WebRenderGPUProcessDisabled) { + mFeatures.mGPUProcess.UserDisable("", ""_ns); + ConfigureWebRender(); + + EXPECT_FALSE(mFeatures.mWr.IsEnabled()); + EXPECT_FALSE(mFeatures.mWrCompositor.IsEnabled()); + EXPECT_FALSE(mFeatures.mWrAngle.IsEnabled()); + EXPECT_FALSE(mFeatures.mWrDComp.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrPartial.IsEnabled()); + EXPECT_FALSE(mFeatures.mWrShaderCache.IsEnabled()); + EXPECT_FALSE(mFeatures.mWrOptimizedShaders.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrScissoredCacheClears.IsEnabled()); + EXPECT_TRUE(mFeatures.mHwCompositing.IsEnabled()); + EXPECT_FALSE(mFeatures.mGPUProcess.IsEnabled()); + EXPECT_TRUE(mFeatures.mD3D11HwAngle.IsEnabled()); +} + +TEST_F(GfxConfigManager, WebRenderIntelBatteryNoHwStretchingNotNightly) { + mIsNightly = mIsEarlyBetaOrEarlier = false; + ++mHwStretchingSupport.mNone; + mScaledResolution = true; + mMockGfxInfo->mHasBattery.ref() = true; + mMockGfxInfo->mVendorId = "0x8086"; + ConfigureWebRender(); + + EXPECT_TRUE(mFeatures.mWr.IsEnabled()); + EXPECT_FALSE(mFeatures.mWrCompositor.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrAngle.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrDComp.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrPartial.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrShaderCache.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrOptimizedShaders.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrScissoredCacheClears.IsEnabled()); + EXPECT_TRUE(mFeatures.mHwCompositing.IsEnabled()); + EXPECT_TRUE(mFeatures.mGPUProcess.IsEnabled()); + EXPECT_TRUE(mFeatures.mD3D11HwAngle.IsEnabled()); +} + +TEST_F(GfxConfigManager, WebRenderNvidiaHighMixedRefreshRateWin10) { + mIsWin11OrLater = false; + mMockGfxInfo->mMaxRefreshRate = 120; + mMockGfxInfo->mHasMixedRefreshRate = true; + ConfigureWebRender(); + + EXPECT_TRUE(mFeatures.mWr.IsEnabled()); + EXPECT_FALSE(mFeatures.mWrCompositor.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrAngle.IsEnabled()); + EXPECT_FALSE(mFeatures.mWrDComp.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrPartial.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrShaderCache.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrOptimizedShaders.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrScissoredCacheClears.IsEnabled()); + EXPECT_TRUE(mFeatures.mHwCompositing.IsEnabled()); + EXPECT_TRUE(mFeatures.mGPUProcess.IsEnabled()); + EXPECT_TRUE(mFeatures.mD3D11HwAngle.IsEnabled()); +} + +TEST_F(GfxConfigManager, WebRenderNvidiaHighMixedRefreshRateWin11) { + mIsWin11OrLater = true; + mMockGfxInfo->mMaxRefreshRate = 120; + mMockGfxInfo->mHasMixedRefreshRate = true; + ConfigureWebRender(); + + EXPECT_TRUE(mFeatures.mWr.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrCompositor.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrAngle.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrDComp.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrPartial.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrShaderCache.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrOptimizedShaders.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrScissoredCacheClears.IsEnabled()); + EXPECT_TRUE(mFeatures.mHwCompositing.IsEnabled()); + EXPECT_TRUE(mFeatures.mGPUProcess.IsEnabled()); + EXPECT_TRUE(mFeatures.mD3D11HwAngle.IsEnabled()); +} + +TEST_F(GfxConfigManager, WebRenderNvidiaHighRefreshRateNotNightly) { + mMockGfxInfo->mMaxRefreshRate = 120; + ConfigureWebRender(); + + EXPECT_TRUE(mFeatures.mWr.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrCompositor.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrAngle.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrDComp.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrPartial.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrShaderCache.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrOptimizedShaders.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrScissoredCacheClears.IsEnabled()); + EXPECT_TRUE(mFeatures.mHwCompositing.IsEnabled()); + EXPECT_TRUE(mFeatures.mGPUProcess.IsEnabled()); + EXPECT_TRUE(mFeatures.mD3D11HwAngle.IsEnabled()); +} + +TEST_F(GfxConfigManager, WebRenderNvidiaLowMixedRefreshRateNotNightly) { + mMockGfxInfo->mMaxRefreshRate = 60; + mMockGfxInfo->mHasMixedRefreshRate = true; + ConfigureWebRender(); + + EXPECT_TRUE(mFeatures.mWr.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrCompositor.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrAngle.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrDComp.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrPartial.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrShaderCache.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrOptimizedShaders.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrScissoredCacheClears.IsEnabled()); + EXPECT_TRUE(mFeatures.mHwCompositing.IsEnabled()); + EXPECT_TRUE(mFeatures.mGPUProcess.IsEnabled()); + EXPECT_TRUE(mFeatures.mD3D11HwAngle.IsEnabled()); +} + +TEST_F(GfxConfigManager, WebRenderSofwareAndNotQualified) { + mMockGfxInfo->mStatusWr = nsIGfxInfo::FEATURE_BLOCKED_DEVICE; + ConfigureWebRender(); + + EXPECT_FALSE(mFeatures.mWr.IsEnabled()); + EXPECT_FALSE(mFeatures.mWrCompositor.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrAngle.IsEnabled()); + EXPECT_FALSE(mFeatures.mWrDComp.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrPartial.IsEnabled()); + EXPECT_FALSE(mFeatures.mWrShaderCache.IsEnabled()); + EXPECT_FALSE(mFeatures.mWrOptimizedShaders.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrScissoredCacheClears.IsEnabled()); + EXPECT_TRUE(mFeatures.mHwCompositing.IsEnabled()); + EXPECT_TRUE(mFeatures.mGPUProcess.IsEnabled()); + EXPECT_TRUE(mFeatures.mD3D11HwAngle.IsEnabled()); +} + +TEST_F(GfxConfigManager, WebRenderForceEnabledEnvvar) { + mWrEnvForceEnabled = true; + mMockGfxInfo->mStatusWr = nsIGfxInfo::FEATURE_BLOCKED_DEVICE; + ConfigureWebRender(); + + EXPECT_TRUE(mFeatures.mWr.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrCompositor.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrAngle.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrDComp.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrPartial.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrShaderCache.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrOptimizedShaders.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrScissoredCacheClears.IsEnabled()); + EXPECT_TRUE(mFeatures.mHwCompositing.IsEnabled()); + EXPECT_TRUE(mFeatures.mGPUProcess.IsEnabled()); + EXPECT_TRUE(mFeatures.mD3D11HwAngle.IsEnabled()); +} + +TEST_F(GfxConfigManager, WebRenderSoftwareAllowedForceEnabledPref) { + mWrForceEnabled = true; + mMockGfxInfo->mStatusWr = nsIGfxInfo::FEATURE_BLOCKED_DEVICE; + ConfigureWebRender(); + + EXPECT_TRUE(mFeatures.mWr.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrCompositor.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrAngle.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrDComp.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrPartial.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrShaderCache.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrOptimizedShaders.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrScissoredCacheClears.IsEnabled()); + EXPECT_TRUE(mFeatures.mHwCompositing.IsEnabled()); + EXPECT_TRUE(mFeatures.mGPUProcess.IsEnabled()); + EXPECT_TRUE(mFeatures.mD3D11HwAngle.IsEnabled()); +} + +TEST_F(GfxConfigManager, WebRenderForceSoftwareForceEnabledEnvvar) { + mWrEnvForceEnabled = true; + mWrSoftwareForceEnabled = true; + mMockGfxInfo->mStatusWr = nsIGfxInfo::FEATURE_BLOCKED_DEVICE; + ConfigureWebRender(); + + EXPECT_FALSE(mFeatures.mWr.IsEnabled()); + EXPECT_FALSE(mFeatures.mWrCompositor.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrAngle.IsEnabled()); + EXPECT_FALSE(mFeatures.mWrDComp.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrPartial.IsEnabled()); + EXPECT_FALSE(mFeatures.mWrShaderCache.IsEnabled()); + EXPECT_FALSE(mFeatures.mWrOptimizedShaders.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrScissoredCacheClears.IsEnabled()); + EXPECT_TRUE(mFeatures.mHwCompositing.IsEnabled()); + EXPECT_TRUE(mFeatures.mGPUProcess.IsEnabled()); + EXPECT_TRUE(mFeatures.mD3D11HwAngle.IsEnabled()); +} + +TEST_F(GfxConfigManager, WebRenderSoftwareReleaseWindowsGPUProcessDisabled) { + mIsNightly = mIsEarlyBetaOrEarlier = false; + mMockGfxInfo->mStatusWr = nsIGfxInfo::FEATURE_BLOCKED_DEVICE; + mFeatures.mGPUProcess.UserDisable("", ""_ns); + ConfigureWebRender(); + + EXPECT_FALSE(mFeatures.mWr.IsEnabled()); + EXPECT_FALSE(mFeatures.mWrCompositor.IsEnabled()); + EXPECT_FALSE(mFeatures.mWrAngle.IsEnabled()); + EXPECT_FALSE(mFeatures.mWrDComp.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrPartial.IsEnabled()); + EXPECT_FALSE(mFeatures.mWrShaderCache.IsEnabled()); + EXPECT_FALSE(mFeatures.mWrOptimizedShaders.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrScissoredCacheClears.IsEnabled()); + EXPECT_TRUE(mFeatures.mHwCompositing.IsEnabled()); + EXPECT_FALSE(mFeatures.mGPUProcess.IsEnabled()); + EXPECT_TRUE(mFeatures.mD3D11HwAngle.IsEnabled()); +} + +TEST_F(GfxConfigManager, WebRenderShaderCacheDisabled) { + mWrShaderCache = Some(false); + ConfigureWebRender(); + + EXPECT_TRUE(mFeatures.mWr.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrCompositor.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrAngle.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrDComp.IsEnabled()); + EXPECT_FALSE(mFeatures.mWrShaderCache.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrOptimizedShaders.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrScissoredCacheClears.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrPartial.IsEnabled()); + EXPECT_TRUE(mFeatures.mHwCompositing.IsEnabled()); + EXPECT_TRUE(mFeatures.mGPUProcess.IsEnabled()); + EXPECT_TRUE(mFeatures.mD3D11HwAngle.IsEnabled()); +} + +TEST_F(GfxConfigManager, WebRenderShaderCacheBlocked) { + mMockGfxInfo->mStatusWrShaderCache = nsIGfxInfo::FEATURE_BLOCKED_DEVICE; + ConfigureWebRender(); + + EXPECT_TRUE(mFeatures.mWr.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrCompositor.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrAngle.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrDComp.IsEnabled()); + EXPECT_FALSE(mFeatures.mWrShaderCache.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrOptimizedShaders.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrScissoredCacheClears.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrPartial.IsEnabled()); + EXPECT_TRUE(mFeatures.mHwCompositing.IsEnabled()); + EXPECT_TRUE(mFeatures.mGPUProcess.IsEnabled()); + EXPECT_TRUE(mFeatures.mD3D11HwAngle.IsEnabled()); +} + +TEST_F(GfxConfigManager, WebRenderScissoredCacheClearsDisabled) { + mWrScissoredCacheClearsEnabled = false; + ConfigureWebRender(); + + EXPECT_TRUE(mFeatures.mWr.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrCompositor.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrAngle.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrDComp.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrShaderCache.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrOptimizedShaders.IsEnabled()); + EXPECT_FALSE(mFeatures.mWrScissoredCacheClears.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrPartial.IsEnabled()); + EXPECT_TRUE(mFeatures.mHwCompositing.IsEnabled()); + EXPECT_TRUE(mFeatures.mGPUProcess.IsEnabled()); + EXPECT_TRUE(mFeatures.mD3D11HwAngle.IsEnabled()); +} + +TEST_F(GfxConfigManager, WebRenderScissoredCacheClearsBlocked) { + mMockGfxInfo->mStatusWrScissoredCacheClears = + nsIGfxInfo::FEATURE_BLOCKED_DEVICE; + ConfigureWebRender(); + + EXPECT_TRUE(mFeatures.mWr.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrCompositor.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrAngle.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrDComp.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrShaderCache.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrOptimizedShaders.IsEnabled()); + EXPECT_FALSE(mFeatures.mWrScissoredCacheClears.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrPartial.IsEnabled()); + EXPECT_TRUE(mFeatures.mHwCompositing.IsEnabled()); + EXPECT_TRUE(mFeatures.mGPUProcess.IsEnabled()); + EXPECT_TRUE(mFeatures.mD3D11HwAngle.IsEnabled()); +} + +TEST_F(GfxConfigManager, WebRenderScissoredCacheClearsForceEnabled) { + mWrScissoredCacheClearsForceEnabled = false; + mMockGfxInfo->mStatusWrScissoredCacheClears = + nsIGfxInfo::FEATURE_BLOCKED_DEVICE; + ConfigureWebRender(); + + EXPECT_TRUE(mFeatures.mWr.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrCompositor.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrAngle.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrDComp.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrShaderCache.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrOptimizedShaders.IsEnabled()); + EXPECT_FALSE(mFeatures.mWrScissoredCacheClears.IsEnabled()); + EXPECT_TRUE(mFeatures.mWrPartial.IsEnabled()); + EXPECT_TRUE(mFeatures.mHwCompositing.IsEnabled()); + EXPECT_TRUE(mFeatures.mGPUProcess.IsEnabled()); + EXPECT_TRUE(mFeatures.mD3D11HwAngle.IsEnabled()); +} diff --git a/gfx/tests/gtest/TestCoord.cpp b/gfx/tests/gtest/TestCoord.cpp new file mode 100644 index 0000000000..bf44e1bd92 --- /dev/null +++ b/gfx/tests/gtest/TestCoord.cpp @@ -0,0 +1,89 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "gtest/gtest.h" + +#include "nsCoord.h" + +namespace mozilla { + +template <typename CoordTyped> +void TestConstructors() { + CoordTyped coord1; + EXPECT_EQ(coord1.value, 0); + + CoordTyped coord2 = 6000; + EXPECT_EQ(coord2.value, 6000); + + EXPECT_EQ(CoordTyped::FromRound(5.3).value, 5); + EXPECT_EQ(CoordTyped::FromRound(5.5).value, 6); + EXPECT_EQ(CoordTyped::FromRound(-2.5).value, -2); + + EXPECT_EQ(CoordTyped::FromTruncate(5.9).value, 5); + EXPECT_EQ(CoordTyped::FromTruncate(-2.7).value, -2); + + EXPECT_EQ(CoordTyped::FromCeil(5.3).value, 6); + EXPECT_EQ(CoordTyped::FromCeil(-2.7).value, -2); + + EXPECT_EQ(CoordTyped::FromFloor(5.9).value, 5); + EXPECT_EQ(CoordTyped::FromFloor(-2.7).value, -3); +} + +template <typename CoordTyped> +void TestComparisonOperators() { + CoordTyped coord1 = 6000; + CoordTyped coord2 = 10000; + + EXPECT_LT(coord1, CoordTyped::kMax); + EXPECT_GT(coord1, CoordTyped::kMin); + EXPECT_NE(coord1, coord2); +} + +template <typename CoordTyped> +void TestArithmeticOperators() { + CoordTyped coord1 = 6000; + CoordTyped coord2 = 10000; + + EXPECT_EQ(coord1 + coord2, CoordTyped(16000)); + EXPECT_EQ(coord2 - coord1, CoordTyped(4000)); + + decltype(CoordTyped::value) scaleInt = 2; + EXPECT_EQ(coord1 * scaleInt, CoordTyped(12000)); + EXPECT_EQ(coord1 / scaleInt, CoordTyped(3000)); + + EXPECT_EQ(coord1 * 2.0, 12000.0); + EXPECT_EQ(coord1 / 2.0, 3000.0); +} + +template <typename CoordTyped> +void TestClamp() { + CoordTyped coord1 = CoordTyped::kMax + 1000; + EXPECT_EQ(coord1.ToMinMaxClamped(), CoordTyped::kMax); + + CoordTyped coord2 = CoordTyped::kMin - 1000; + EXPECT_EQ(coord2.ToMinMaxClamped(), CoordTyped::kMin); + + CoordTyped coord3 = 12000; + EXPECT_EQ(coord3.ToMinMaxClamped(), CoordTyped(12000)); +} + +TEST(Gfx, TestAuCoord) +{ + TestConstructors<AuCoord>(); + TestComparisonOperators<AuCoord>(); + TestArithmeticOperators<AuCoord>(); + TestClamp<AuCoord>(); +} + +TEST(Gfx, TestAuCoord64) +{ + TestConstructors<AuCoord64>(); + TestComparisonOperators<AuCoord64>(); + TestArithmeticOperators<AuCoord64>(); + TestClamp<AuCoord64>(); +} + +} // namespace mozilla diff --git a/gfx/tests/gtest/TestGfxWidgets.cpp b/gfx/tests/gtest/TestGfxWidgets.cpp new file mode 100644 index 0000000000..9cadfa7382 --- /dev/null +++ b/gfx/tests/gtest/TestGfxWidgets.cpp @@ -0,0 +1,111 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "gtest/gtest.h" +#include "GfxDriverInfo.h" +#include "nsVersionComparator.h" + +using namespace mozilla::widget; + +TEST(GfxWidgets, Split) +{ + char aStr[8], bStr[8], cStr[8], dStr[8]; + + ASSERT_TRUE(SplitDriverVersion("33.4.3.22", aStr, bStr, cStr, dStr)); + ASSERT_TRUE(atoi(aStr) == 33 && atoi(bStr) == 4 && atoi(cStr) == 3 && + atoi(dStr) == 22); + + ASSERT_TRUE(SplitDriverVersion("28.74.0.0", aStr, bStr, cStr, dStr)); + ASSERT_TRUE(atoi(aStr) == 28 && atoi(bStr) == 74 && atoi(cStr) == 0 && + atoi(dStr) == 0); + + ASSERT_TRUE(SplitDriverVersion("132.0.0.0", aStr, bStr, cStr, dStr)); + ASSERT_TRUE(atoi(aStr) == 132 && atoi(bStr) == 0 && atoi(cStr) == 0 && + atoi(dStr) == 0); + + ASSERT_TRUE(SplitDriverVersion("2.3.0.0", aStr, bStr, cStr, dStr)); + ASSERT_TRUE(atoi(aStr) == 2 && atoi(bStr) == 3 && atoi(cStr) == 0 && + atoi(dStr) == 0); + + ASSERT_TRUE(SplitDriverVersion("25.4.0.8", aStr, bStr, cStr, dStr)); + ASSERT_TRUE(atoi(aStr) == 25 && atoi(bStr) == 4 && atoi(cStr) == 0 && + atoi(dStr) == 8); + + ASSERT_TRUE(SplitDriverVersion("424.143.84437.3", aStr, bStr, cStr, dStr)); + ASSERT_TRUE(atoi(aStr) == 424 && atoi(bStr) == 143 && atoi(cStr) == 8443 && + atoi(dStr) == 3); + + ASSERT_FALSE(SplitDriverVersion("25.4.0.8.", aStr, bStr, cStr, dStr)); + ASSERT_TRUE(atoi(aStr) == 25 && atoi(bStr) == 4 && atoi(cStr) == 0 && + atoi(dStr) == 8); + + ASSERT_TRUE(SplitDriverVersion("424.143.8.3143243", aStr, bStr, cStr, dStr)); + ASSERT_TRUE(atoi(aStr) == 424 && atoi(bStr) == 143 && atoi(cStr) == 8 && + atoi(dStr) == 3143); + + ASSERT_FALSE(SplitDriverVersion("25.4.0.8..", aStr, bStr, cStr, dStr)); + ASSERT_TRUE(atoi(aStr) == 25 && atoi(bStr) == 4 && atoi(cStr) == 0 && + atoi(dStr) == 8); + + ASSERT_FALSE( + SplitDriverVersion("424.143.8.3143243.", aStr, bStr, cStr, dStr)); + ASSERT_TRUE(atoi(aStr) == 424 && atoi(bStr) == 143 && atoi(cStr) == 8 && + atoi(dStr) == 3143); + + ASSERT_FALSE(SplitDriverVersion("25.4.0.8.13", aStr, bStr, cStr, dStr)); + ASSERT_TRUE(atoi(aStr) == 25 && atoi(bStr) == 4 && atoi(cStr) == 0 && + atoi(dStr) == 8); + + ASSERT_FALSE(SplitDriverVersion("4.1.8.13.24.35", aStr, bStr, cStr, dStr)); + ASSERT_TRUE(atoi(aStr) == 4 && atoi(bStr) == 1 && atoi(cStr) == 8 && + atoi(dStr) == 13); + + ASSERT_TRUE(SplitDriverVersion("28...74", aStr, bStr, cStr, dStr)); + ASSERT_TRUE(atoi(aStr) == 28 && atoi(bStr) == 0 && atoi(cStr) == 0 && + atoi(dStr) == 74); + + ASSERT_FALSE(SplitDriverVersion("4.1.8.13.24.35", aStr, bStr, cStr, dStr)); + ASSERT_TRUE(atoi(aStr) == 4 && atoi(bStr) == 1 && atoi(cStr) == 8 && + atoi(dStr) == 13); + + ASSERT_TRUE(SplitDriverVersion("35..42.0", aStr, bStr, cStr, dStr)); + ASSERT_TRUE(atoi(aStr) == 35 && atoi(bStr) == 0 && atoi(cStr) == 42 && + atoi(dStr) == 0); +} + +TEST(GfxWidgets, Versioning) +{ + ASSERT_TRUE(mozilla::Version("0") < mozilla::Version("41.0a1")); + ASSERT_TRUE(mozilla::Version("39.0.5b7") < mozilla::Version("41.0a1")); + ASSERT_TRUE(mozilla::Version("18.0.5b7") < mozilla::Version("18.2")); + ASSERT_TRUE(mozilla::Version("30.0.5b7") < mozilla::Version("41.0b9")); + ASSERT_TRUE(mozilla::Version("100") > mozilla::Version("43.0a1")); + ASSERT_FALSE(mozilla::Version("42.0") < mozilla::Version("42.0")); + ASSERT_TRUE(mozilla::Version("42.0b2") < mozilla::Version("42.0")); + ASSERT_TRUE(mozilla::Version("42.0b2") < mozilla::Version("42")); + ASSERT_TRUE(mozilla::Version("42.0b2") < mozilla::Version("43.0a1")); + ASSERT_TRUE(mozilla::Version("42") < mozilla::Version("43.0a1")); + ASSERT_TRUE(mozilla::Version("42.0") < mozilla::Version("43.0a1")); + ASSERT_TRUE(mozilla::Version("42.0.5") < mozilla::Version("43.0a1")); + ASSERT_TRUE(mozilla::Version("42.1") < mozilla::Version("43.0a1")); + ASSERT_TRUE(mozilla::Version("42.0a1") < mozilla::Version("42")); + ASSERT_TRUE(mozilla::Version("42.0a1") < mozilla::Version("42.0.5")); + ASSERT_TRUE(mozilla::Version("42.0b7") < mozilla::Version("42.0.5")); + ASSERT_TRUE(mozilla::Version("") == mozilla::Version("0")); + ASSERT_TRUE(mozilla::Version("1b1b") == mozilla::Version("1b1b")); + ASSERT_TRUE(mozilla::Version("1b1b") < mozilla::Version("1b1c")); + ASSERT_TRUE(mozilla::Version("1b1b") < mozilla::Version("1b1d")); + ASSERT_TRUE(mozilla::Version("1b1c") > mozilla::Version("1b1b")); + ASSERT_TRUE(mozilla::Version("1b1d") > mozilla::Version("1b1b")); + ASSERT_TRUE(mozilla::Version("110") < mozilla::Version("110.0.1")); + ASSERT_TRUE(mozilla::Version("110.*") >= mozilla::Version("110.0.1")); + + // Note these two; one would expect for 42.0b1 and 42b1 to compare the + // same, but they do not. If this ever changes, we want to know, so + // leave the test here to fail. + ASSERT_TRUE(mozilla::Version("42.0a1") < mozilla::Version("42.0b2")); + ASSERT_FALSE(mozilla::Version("42.0a1") < mozilla::Version("42b2")); +} diff --git a/gfx/tests/gtest/TestMatrix.cpp b/gfx/tests/gtest/TestMatrix.cpp new file mode 100644 index 0000000000..e55e8e793b --- /dev/null +++ b/gfx/tests/gtest/TestMatrix.cpp @@ -0,0 +1,145 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "gtest/gtest.h" +#include "mozilla/gfx/Matrix.h" + +using namespace mozilla; +using namespace mozilla::gfx; + +TEST(Matrix, TransformAndClipRect) +{ + Rect c(100, 100, 100, 100); + Matrix4x4 m; + EXPECT_TRUE(m.TransformAndClipBounds(Rect(50, 50, 20, 20), c).IsEmpty()); + EXPECT_TRUE(m.TransformAndClipBounds(Rect(250, 50, 20, 20), c).IsEmpty()); + EXPECT_TRUE(m.TransformAndClipBounds(Rect(250, 250, 20, 20), c).IsEmpty()); + EXPECT_TRUE(m.TransformAndClipBounds(Rect(50, 250, 20, 20), c).IsEmpty()); + + EXPECT_TRUE(m.TransformAndClipBounds(Rect(50, 50, 100, 20), c).IsEmpty()); + EXPECT_TRUE(m.TransformAndClipBounds(Rect(150, 50, 100, 20), c).IsEmpty()); + EXPECT_TRUE(m.TransformAndClipBounds(Rect(50, 250, 100, 20), c).IsEmpty()); + EXPECT_TRUE(m.TransformAndClipBounds(Rect(150, 250, 100, 20), c).IsEmpty()); + + EXPECT_TRUE(m.TransformAndClipBounds(Rect(50, 50, 20, 100), c).IsEmpty()); + EXPECT_TRUE(m.TransformAndClipBounds(Rect(50, 150, 20, 100), c).IsEmpty()); + EXPECT_TRUE(m.TransformAndClipBounds(Rect(250, 50, 20, 100), c).IsEmpty()); + EXPECT_TRUE(m.TransformAndClipBounds(Rect(250, 150, 20, 100), c).IsEmpty()); + + EXPECT_TRUE(m.TransformAndClipBounds(Rect(50, 50, 100, 100), c) + .IsEqualInterior(Rect(100, 100, 50, 50))); + EXPECT_TRUE(m.TransformAndClipBounds(Rect(150, 50, 100, 100), c) + .IsEqualInterior(Rect(150, 100, 50, 50))); + EXPECT_TRUE(m.TransformAndClipBounds(Rect(150, 150, 100, 100), c) + .IsEqualInterior(Rect(150, 150, 50, 50))); + EXPECT_TRUE(m.TransformAndClipBounds(Rect(50, 150, 100, 100), c) + .IsEqualInterior(Rect(100, 150, 50, 50))); + + EXPECT_TRUE(m.TransformAndClipBounds(Rect(110, 110, 80, 80), c) + .IsEqualInterior(Rect(110, 110, 80, 80))); + + EXPECT_TRUE(m.TransformAndClipBounds(Rect(50, 50, 200, 200), c) + .IsEqualInterior(Rect(100, 100, 100, 100))); + + EXPECT_TRUE(m.TransformAndClipBounds(Rect(50, 50, 200, 100), c) + .IsEqualInterior(Rect(100, 100, 100, 50))); + EXPECT_TRUE(m.TransformAndClipBounds(Rect(50, 150, 200, 100), c) + .IsEqualInterior(Rect(100, 150, 100, 50))); + EXPECT_TRUE(m.TransformAndClipBounds(Rect(50, 50, 100, 200), c) + .IsEqualInterior(Rect(100, 100, 50, 100))); + EXPECT_TRUE(m.TransformAndClipBounds(Rect(150, 50, 100, 200), c) + .IsEqualInterior(Rect(150, 100, 50, 100))); + + Matrix4x4 m2 = Matrix4x4::From2D(Matrix(22.68, 0, 0, 12, 16, 164)); + EXPECT_TRUE( + m2.TransformAndClipBounds(Rect(0, 0, 100, 100), Rect(1024, 1024, 0, 0)) + .IsEmpty()); + + // Empty rectangles should still have meaningful corners. + EXPECT_TRUE(m.TransformAndClipBounds(Rect(150, 50, 0, 200), c) + .IsEqualEdges(Rect(150, 100, 0, 100))); + EXPECT_TRUE(m.TransformAndClipBounds(Rect(150, 150, 0, 0), c) + .IsEqualEdges(Rect(150, 150, 0, 0))); + EXPECT_TRUE(m.TransformAndClipBounds(Rect(150, 100, 300, 0), c) + .IsEqualEdges(Rect(150, 100, 50, 0))); +} + +TEST(Matrix, RotateTransform) +{ + gfx::Matrix4x4 transformMatrix; + gfx::Point3D trans, scale; + gfx::Quaternion orient; + + auto floor = [&](float aValue, int aDecimal) { + const int digit = pow(10, aDecimal); + const float result = (int)(aValue * digit); + return result / digit; + }; + + // Test rotate 45 degree on x-axis. + gfx::Quaternion expectedOrient(0.382f, 0.0f, 0.0f, 0.923f); + transformMatrix.RotateX(0.785f); + // the orient would be (x:0.3825, y:0, z:0, w: 0.9239) + transformMatrix.Decompose(trans, orient, scale); + EXPECT_EQ(floor(orient.x, 3), expectedOrient.x); + EXPECT_EQ(floor(orient.y, 3), expectedOrient.y); + EXPECT_EQ(floor(orient.z, 3), expectedOrient.z); + EXPECT_EQ(floor(orient.w, 3), expectedOrient.w); + + // Test set rotate matrix from a quaternion and + // compare it with the result from decompose. + transformMatrix = gfx::Matrix4x4(); + transformMatrix.SetRotationFromQuaternion(orient); + transformMatrix.Decompose(trans, orient, scale); + EXPECT_EQ(floor(orient.x, 3), expectedOrient.x); + EXPECT_EQ(floor(orient.y, 3), expectedOrient.y); + EXPECT_EQ(floor(orient.z, 3), expectedOrient.z); + EXPECT_EQ(floor(orient.w, 3), expectedOrient.w); + + // Test rotate -45 degree on axis: (0.577f, 0.577f, 0.577f). + transformMatrix = gfx::Matrix4x4(); + transformMatrix.SetRotateAxisAngle(0.577f, 0.577f, 0.577f, -0.785f); + // the orient would be (x:-0.2208, y:-0.2208, z:-0.2208, w: 0.9239) + transformMatrix.Decompose(trans, orient, scale); + + expectedOrient.Set(-0.220f, -0.220f, -0.220f, 0.923f); + EXPECT_EQ(floor(orient.x, 3), expectedOrient.x); + EXPECT_EQ(floor(orient.y, 3), expectedOrient.y); + EXPECT_EQ(floor(orient.z, 3), expectedOrient.z); + EXPECT_EQ(floor(orient.w, 3), expectedOrient.w); + + // Test set rotate matrix from a quaternion and + // compare it with the result from decompose. + transformMatrix = gfx::Matrix4x4(); + transformMatrix.SetRotationFromQuaternion(orient); + transformMatrix.Decompose(trans, orient, scale); + EXPECT_EQ(floor(orient.x, 3), expectedOrient.x); + EXPECT_EQ(floor(orient.y, 3), expectedOrient.y); + EXPECT_EQ(floor(orient.z, 3), expectedOrient.z); + EXPECT_EQ(floor(orient.w, 3), expectedOrient.w); +} + +TEST(Matrix4x4Flagged, Mult) +{ + Matrix4x4Flagged simple = + Matrix4x4::Translation(Point(42, 42)) * Matrix4x4::Scaling(3, 3, 1); + // For the general matrix, put a value in every field to make sure + // nothing gets dropped. + Matrix4x4 general(2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2); + + // Use Matrix4x4::operator*(Matrix4x4). + // For the purposes of this test, assume that's correct. + Matrix4x4Flagged realResult = Matrix4x4Flagged(simple.GetMatrix() * general); + + // Check that Matrix4x4Flagged::operator*(Matrix4x4Flagged) produces the same + // result. + Matrix4x4Flagged flaggedResult = simple * Matrix4x4Flagged(general); + EXPECT_EQ(realResult, flaggedResult); + + // Check that Matrix4x4Flagged::operator*(Matrix4x4) produces the same result. + Matrix4x4Flagged mixedResult = simple * general; + EXPECT_EQ(realResult, mixedResult); +} diff --git a/gfx/tests/gtest/TestMoz2D.cpp b/gfx/tests/gtest/TestMoz2D.cpp new file mode 100644 index 0000000000..50854cdc98 --- /dev/null +++ b/gfx/tests/gtest/TestMoz2D.cpp @@ -0,0 +1,41 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "gtest/gtest.h" +#include "TestBase.h" +#include "TestPoint.h" +#include "TestScaling.h" +#include "TestBugs.h" + +TEST(Moz2D, Bugs) +{ + TestBugs* test = new TestBugs(); + int failures = 0; + test->RunTests(&failures); + delete test; + + ASSERT_EQ(failures, 0); +} + +TEST(Moz2D, Point) +{ + TestBase* test = new TestPoint(); + int failures = 0; + test->RunTests(&failures); + delete test; + + ASSERT_EQ(failures, 0); +} + +TEST(Moz2D, Scaling) +{ + TestBase* test = new TestScaling(); + int failures = 0; + test->RunTests(&failures); + delete test; + + ASSERT_EQ(failures, 0); +} diff --git a/gfx/tests/gtest/TestPolygon.cpp b/gfx/tests/gtest/TestPolygon.cpp new file mode 100644 index 0000000000..168f7b46d9 --- /dev/null +++ b/gfx/tests/gtest/TestPolygon.cpp @@ -0,0 +1,104 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "gtest/gtest.h" + +#include "PolygonTestUtils.h" + +#include "nsTArray.h" +#include "Point.h" +#include "Polygon.h" +#include "Triangle.h" + +using namespace mozilla::gfx; +typedef mozilla::gfx::Polygon MozPolygon; + +TEST(MozPolygon, TriangulateRectangle) +{ + const MozPolygon p{ + Point4D(0.0f, 0.0f, 1.0f, 1.0f), Point4D(0.0f, 1.0f, 1.0f, 1.0f), + Point4D(1.0f, 1.0f, 1.0f, 1.0f), Point4D(1.0f, 0.0f, 1.0f, 1.0f)}; + + const nsTArray<Triangle> triangles = p.ToTriangles(); + const nsTArray<Triangle> expected = { + Triangle(Point(0.0f, 0.0f), Point(0.0f, 1.0f), Point(1.0f, 1.0f)), + Triangle(Point(0.0f, 0.0f), Point(1.0f, 1.0f), Point(1.0f, 0.0f))}; + + AssertArrayEQ(triangles, expected); +} + +TEST(MozPolygon, TriangulatePentagon) +{ + const MozPolygon p{ + Point4D(0.0f, 0.0f, 1.0f, 1.0f), Point4D(0.0f, 1.0f, 1.0f, 1.0f), + Point4D(0.5f, 1.5f, 1.0f, 1.0f), Point4D(1.0f, 1.0f, 1.0f, 1.0f), + Point4D(1.0f, 0.0f, 1.0f, 1.0f)}; + + const nsTArray<Triangle> triangles = p.ToTriangles(); + const nsTArray<Triangle> expected = { + Triangle(Point(0.0f, 0.0f), Point(0.0f, 1.0f), Point(0.5f, 1.5f)), + Triangle(Point(0.0f, 0.0f), Point(0.5f, 1.5f), Point(1.0f, 1.0f)), + Triangle(Point(0.0f, 0.0f), Point(1.0f, 1.0f), Point(1.0f, 0.0f))}; + + AssertArrayEQ(triangles, expected); +} + +static void TestClipRect(const MozPolygon& aPolygon, + const MozPolygon& aExpected, const Rect& aRect) { + const MozPolygon res = aPolygon.ClipPolygon(MozPolygon::FromRect(aRect)); + EXPECT_TRUE(res == aExpected); +} + +TEST(MozPolygon, ClipRectangle) +{ + MozPolygon polygon{ + Point4D(0.0f, 0.0f, 0.0f, 1.0f), Point4D(0.0f, 1.0f, 0.0f, 1.0f), + Point4D(1.0f, 1.0f, 0.0f, 1.0f), Point4D(1.0f, 0.0f, 0.0f, 1.0f)}; + TestClipRect(polygon, polygon, Rect(0.0f, 0.0f, 1.0f, 1.0f)); + + MozPolygon expected = MozPolygon{ + Point4D(0.0f, 0.0f, 0.0f, 1.0f), Point4D(0.0f, 0.8f, 0.0f, 1.0f), + Point4D(0.8f, 0.8f, 0.0f, 1.0f), Point4D(0.8f, 0.0f, 0.0f, 1.0f)}; + TestClipRect(polygon, expected, Rect(0.0f, 0.0f, 0.8f, 0.8f)); + + expected = MozPolygon{ + Point4D(0.2f, 0.2f, 0.0f, 1.0f), Point4D(0.2f, 1.0f, 0.0f, 1.0f), + Point4D(1.0f, 1.0f, 0.0f, 1.0f), Point4D(1.0f, 0.2f, 0.0f, 1.0f)}; + TestClipRect(polygon, expected, Rect(0.2f, 0.2f, 0.8f, 0.8f)); + + expected = MozPolygon{ + Point4D(0.2f, 0.2f, 0.0f, 1.0f), Point4D(0.2f, 0.8f, 0.0f, 1.0f), + Point4D(0.8f, 0.8f, 0.0f, 1.0f), Point4D(0.8f, 0.2f, 0.0f, 1.0f)}; + TestClipRect(polygon, expected, Rect(0.2f, 0.2f, 0.6f, 0.6f)); +} + +TEST(MozPolygon, ClipTriangle) +{ + MozPolygon clipped, expected; + const MozPolygon polygon{Point4D(0.0f, 0.0f, 0.0f, 1.0f), + Point4D(0.0f, 1.0f, 0.0f, 1.0f), + Point4D(1.0f, 1.0f, 0.0f, 1.0f)}; + + expected = MozPolygon{Point4D(0.0f, 0.0f, 0.0f, 1.0f), + Point4D(0.0f, 1.0f, 0.0f, 1.0f), + Point4D(1.0f, 1.0f, 0.0f, 1.0f)}; + TestClipRect(polygon, expected, Rect(0.0f, 0.0f, 1.0f, 1.0f)); + + expected = MozPolygon{Point4D(0.0f, 0.0f, 0.0f, 1.0f), + Point4D(0.0f, 0.8f, 0.0f, 1.0f), + Point4D(0.8f, 0.8f, 0.0f, 1.0f)}; + TestClipRect(polygon, expected, Rect(0.0f, 0.0f, 0.8f, 0.8f)); + + expected = MozPolygon{Point4D(0.2f, 0.2f, 0.0f, 1.0f), + Point4D(0.2f, 1.0f, 0.0f, 1.0f), + Point4D(1.0f, 1.0f, 0.0f, 1.0f)}; + TestClipRect(polygon, expected, Rect(0.2f, 0.2f, 0.8f, 0.8f)); + + expected = MozPolygon{Point4D(0.2f, 0.2f, 0.0f, 1.0f), + Point4D(0.2f, 0.8f, 0.0f, 1.0f), + Point4D(0.8f, 0.8f, 0.0f, 1.0f)}; + TestClipRect(polygon, expected, Rect(0.2f, 0.2f, 0.6f, 0.6f)); +} diff --git a/gfx/tests/gtest/TestQcms.cpp b/gfx/tests/gtest/TestQcms.cpp new file mode 100644 index 0000000000..de42ecff38 --- /dev/null +++ b/gfx/tests/gtest/TestQcms.cpp @@ -0,0 +1,410 @@ +/* vim:set ts=2 sw=2 sts=2 et: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +#include "gtest/gtest.h" +#include "gtest/MozGTestBench.h" +#include "gmock/gmock.h" + +#include "mozilla/ArrayUtils.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/SSE.h" +#include "mozilla/arm.h" +#include "qcms.h" +#include "qcmsint.h" + +#include <cmath> + +/* SSEv1 is only included in non-Windows or non-x86-64-bit builds. */ +#if defined(MOZILLA_MAY_SUPPORT_SSE) && \ + (!(defined(_MSC_VER) && defined(_M_AMD64))) +# define QCMS_MAY_SUPPORT_SSE +#endif + +using namespace mozilla; + +static bool CmpRgbChannel(const uint8_t* aRef, const uint8_t* aTest, + size_t aIndex) { + return std::abs(static_cast<int8_t>(aRef[aIndex] - aTest[aIndex])) <= 1; +} + +template <bool kSwapRB, bool kHasAlpha> +static bool CmpRgbBufferImpl(const uint8_t* aRefBuffer, + const uint8_t* aTestBuffer, size_t aPixels) { + const size_t pixelSize = kHasAlpha ? 4 : 3; + if (memcmp(aRefBuffer, aTestBuffer, aPixels * pixelSize) == 0) { + return true; + } + + const size_t kRIndex = kSwapRB ? 2 : 0; + const size_t kGIndex = 1; + const size_t kBIndex = kSwapRB ? 0 : 2; + const size_t kAIndex = 3; + + size_t remaining = aPixels; + const uint8_t* ref = aRefBuffer; + const uint8_t* test = aTestBuffer; + while (remaining > 0) { + if (!CmpRgbChannel(ref, test, kRIndex) || + !CmpRgbChannel(ref, test, kGIndex) || + !CmpRgbChannel(ref, test, kBIndex) || + (kHasAlpha && ref[kAIndex] != test[kAIndex])) { + EXPECT_EQ(test[kRIndex], ref[kRIndex]); + EXPECT_EQ(test[kGIndex], ref[kGIndex]); + EXPECT_EQ(test[kBIndex], ref[kBIndex]); + if (kHasAlpha) { + EXPECT_EQ(test[kAIndex], ref[kAIndex]); + } + return false; + } + + --remaining; + ref += pixelSize; + test += pixelSize; + } + + return true; +} + +template <bool kSwapRB, bool kHasAlpha> +static size_t GetRgbInputBufferImpl(UniquePtr<uint8_t[]>& aOutBuffer) { + const uint8_t colorSamples[] = {0, 5, 16, 43, 101, 127, 182, 255}; + const size_t colorSampleMax = sizeof(colorSamples) / sizeof(uint8_t); + const size_t pixelSize = kHasAlpha ? 4 : 3; + const size_t pixelCount = colorSampleMax * colorSampleMax * 256 * 3; + + aOutBuffer = MakeUnique<uint8_t[]>(pixelCount * pixelSize); + if (!aOutBuffer) { + return 0; + } + + const size_t kRIndex = kSwapRB ? 2 : 0; + const size_t kGIndex = 1; + const size_t kBIndex = kSwapRB ? 0 : 2; + const size_t kAIndex = 3; + + // Sample every red pixel value with a subset of green and blue. + uint8_t* color = aOutBuffer.get(); + for (uint16_t r = 0; r < 256; ++r) { + for (uint8_t g : colorSamples) { + for (uint8_t b : colorSamples) { + color[kRIndex] = r; + color[kGIndex] = g; + color[kBIndex] = b; + if (kHasAlpha) { + color[kAIndex] = 0x80; + } + color += pixelSize; + } + } + } + + // Sample every green pixel value with a subset of red and blue. + for (uint8_t r : colorSamples) { + for (uint16_t g = 0; g < 256; ++g) { + for (uint8_t b : colorSamples) { + color[kRIndex] = r; + color[kGIndex] = g; + color[kBIndex] = b; + if (kHasAlpha) { + color[kAIndex] = 0x80; + } + color += pixelSize; + } + } + } + + // Sample every blue pixel value with a subset of red and green. + for (uint8_t r : colorSamples) { + for (uint8_t g : colorSamples) { + for (uint16_t b = 0; b < 256; ++b) { + color[kRIndex] = r; + color[kGIndex] = g; + color[kBIndex] = b; + if (kHasAlpha) { + color[kAIndex] = 0x80; + } + color += pixelSize; + } + } + } + + return pixelCount; +} + +static size_t GetRgbInputBuffer(UniquePtr<uint8_t[]>& aOutBuffer) { + return GetRgbInputBufferImpl<false, false>(aOutBuffer); +} + +static size_t GetRgbaInputBuffer(UniquePtr<uint8_t[]>& aOutBuffer) { + return GetRgbInputBufferImpl<false, true>(aOutBuffer); +} + +static size_t GetBgraInputBuffer(UniquePtr<uint8_t[]>& aOutBuffer) { + return GetRgbInputBufferImpl<true, true>(aOutBuffer); +} + +static bool CmpRgbBuffer(const uint8_t* aRefBuffer, const uint8_t* aTestBuffer, + size_t aPixels) { + return CmpRgbBufferImpl<false, false>(aRefBuffer, aTestBuffer, aPixels); +} + +static bool CmpRgbaBuffer(const uint8_t* aRefBuffer, const uint8_t* aTestBuffer, + size_t aPixels) { + return CmpRgbBufferImpl<false, true>(aRefBuffer, aTestBuffer, aPixels); +} + +static bool CmpBgraBuffer(const uint8_t* aRefBuffer, const uint8_t* aTestBuffer, + size_t aPixels) { + return CmpRgbBufferImpl<true, true>(aRefBuffer, aTestBuffer, aPixels); +} + +static void ClearRgbBuffer(uint8_t* aBuffer, size_t aPixels) { + if (aBuffer) { + memset(aBuffer, 0, aPixels * 3); + } +} + +static void ClearRgbaBuffer(uint8_t* aBuffer, size_t aPixels) { + if (aBuffer) { + memset(aBuffer, 0, aPixels * 4); + } +} + +static UniquePtr<uint8_t[]> GetRgbOutputBuffer(size_t aPixels) { + UniquePtr<uint8_t[]> buffer = MakeUnique<uint8_t[]>(aPixels * 3); + ClearRgbBuffer(buffer.get(), aPixels); + return buffer; +} + +static UniquePtr<uint8_t[]> GetRgbaOutputBuffer(size_t aPixels) { + UniquePtr<uint8_t[]> buffer = MakeUnique<uint8_t[]>(aPixels * 4); + ClearRgbaBuffer(buffer.get(), aPixels); + return buffer; +} + +class GfxQcms_ProfilePairBase : public ::testing::Test { + protected: + GfxQcms_ProfilePairBase() + : mInProfile(nullptr), + mOutProfile(nullptr), + mTransform(nullptr), + mPixels(0), + mStorageType(QCMS_DATA_RGB_8), + mPrecache(false) {} + + void SetUp() override { + // XXX: This means that we can't have qcms v2 unit test + // without changing the qcms API. + qcms_enable_iccv4(); + } + + void TearDown() override { + if (mInProfile) { + qcms_profile_release(mInProfile); + } + if (mOutProfile) { + qcms_profile_release(mOutProfile); + } + if (mTransform) { + qcms_transform_release(mTransform); + } + } + + bool SetTransform(qcms_transform* aTransform) { + if (mTransform) { + qcms_transform_release(mTransform); + } + mTransform = aTransform; + return !!mTransform; + } + + bool SetTransform(qcms_data_type aType) { + return SetTransform(qcms_transform_create(mInProfile, aType, mOutProfile, + aType, QCMS_INTENT_DEFAULT)); + } + + bool SetBuffers(qcms_data_type aType) { + switch (aType) { + case QCMS_DATA_RGB_8: + mPixels = GetRgbInputBuffer(mInput); + mRef = GetRgbOutputBuffer(mPixels); + mOutput = GetRgbOutputBuffer(mPixels); + break; + case QCMS_DATA_RGBA_8: + mPixels = GetRgbaInputBuffer(mInput); + mRef = GetRgbaOutputBuffer(mPixels); + mOutput = GetRgbaOutputBuffer(mPixels); + break; + case QCMS_DATA_BGRA_8: + mPixels = GetBgraInputBuffer(mInput); + mRef = GetRgbaOutputBuffer(mPixels); + mOutput = GetRgbaOutputBuffer(mPixels); + break; + default: + MOZ_ASSERT_UNREACHABLE("Unknown type!"); + break; + } + + mStorageType = aType; + return mInput && mOutput && mRef && mPixels > 0u; + } + + void ClearOutputBuffer() { + switch (mStorageType) { + case QCMS_DATA_RGB_8: + ClearRgbBuffer(mOutput.get(), mPixels); + break; + case QCMS_DATA_RGBA_8: + case QCMS_DATA_BGRA_8: + ClearRgbaBuffer(mOutput.get(), mPixels); + break; + default: + MOZ_ASSERT_UNREACHABLE("Unknown type!"); + break; + } + } + + void ProduceRef(transform_fn_t aFn) { + aFn(mTransform, mInput.get(), mRef.get(), mPixels); + } + + void CopyInputToRef() { + size_t pixelSize = 0; + switch (mStorageType) { + case QCMS_DATA_RGB_8: + pixelSize = 3; + break; + case QCMS_DATA_RGBA_8: + case QCMS_DATA_BGRA_8: + pixelSize = 4; + break; + default: + MOZ_ASSERT_UNREACHABLE("Unknown type!"); + break; + } + + memcpy(mRef.get(), mInput.get(), mPixels * pixelSize); + } + + void ProduceOutput(transform_fn_t aFn) { + ClearOutputBuffer(); + aFn(mTransform, mInput.get(), mOutput.get(), mPixels); + } + + bool VerifyOutput(const UniquePtr<uint8_t[]>& aBuf) { + switch (mStorageType) { + case QCMS_DATA_RGB_8: + return CmpRgbBuffer(aBuf.get(), mOutput.get(), mPixels); + case QCMS_DATA_RGBA_8: + return CmpRgbaBuffer(aBuf.get(), mOutput.get(), mPixels); + case QCMS_DATA_BGRA_8: + return CmpBgraBuffer(aBuf.get(), mOutput.get(), mPixels); + default: + MOZ_ASSERT_UNREACHABLE("Unknown type!"); + break; + } + + return false; + } + + bool ProduceVerifyOutput(transform_fn_t aFn) { + ProduceOutput(aFn); + return VerifyOutput(mRef); + } + + void PrecacheOutput() { + qcms_profile_precache_output_transform(mOutProfile); + mPrecache = true; + } + + qcms_profile* mInProfile; + qcms_profile* mOutProfile; + qcms_transform* mTransform; + + UniquePtr<uint8_t[]> mInput; + UniquePtr<uint8_t[]> mOutput; + UniquePtr<uint8_t[]> mRef; + size_t mPixels; + qcms_data_type mStorageType; + bool mPrecache; +}; + +class GfxQcms_sRGB_To_sRGB : public GfxQcms_ProfilePairBase { + protected: + void SetUp() override { + GfxQcms_ProfilePairBase::SetUp(); + mInProfile = qcms_profile_sRGB(); + mOutProfile = qcms_profile_sRGB(); + } +}; + +class GfxQcms_sRGB_To_SamsungSyncmaster : public GfxQcms_ProfilePairBase { + protected: + void SetUp() override { + GfxQcms_ProfilePairBase::SetUp(); + mInProfile = qcms_profile_sRGB(); + mOutProfile = qcms_profile_from_path("lcms_samsung_syncmaster.icc"); + } +}; + +class GfxQcms_sRGB_To_ThinkpadW540 : public GfxQcms_ProfilePairBase { + protected: + void SetUp() override { + GfxQcms_ProfilePairBase::SetUp(); + mInProfile = qcms_profile_sRGB(); + mOutProfile = qcms_profile_from_path("lcms_thinkpad_w540.icc"); + } +}; + +TEST_F(GfxQcms_sRGB_To_sRGB, TransformIdentity) { + PrecacheOutput(); + SetBuffers(QCMS_DATA_RGB_8); + SetTransform(QCMS_DATA_RGB_8); + qcms_transform_data(mTransform, mInput.get(), mOutput.get(), mPixels); + EXPECT_TRUE(VerifyOutput(mInput)); +} + +class GfxQcmsPerf_Base : public GfxQcms_sRGB_To_ThinkpadW540 { + protected: + explicit GfxQcmsPerf_Base(qcms_data_type aType) { mStorageType = aType; } + + void TransformPerf() { ProduceRef(qcms_transform_data_rgb_out_lut_precache); } + + void TransformPlatformPerf() { + qcms_transform_data(mTransform, mInput.get(), mRef.get(), mPixels); + } + + void SetUp() override { + GfxQcms_sRGB_To_ThinkpadW540::SetUp(); + PrecacheOutput(); + SetBuffers(mStorageType); + SetTransform(mStorageType); + } +}; + +class GfxQcmsPerf_Rgb : public GfxQcmsPerf_Base { + protected: + GfxQcmsPerf_Rgb() : GfxQcmsPerf_Base(QCMS_DATA_RGB_8) {} +}; + +class GfxQcmsPerf_Rgba : public GfxQcmsPerf_Base { + protected: + GfxQcmsPerf_Rgba() : GfxQcmsPerf_Base(QCMS_DATA_RGBA_8) {} +}; + +class GfxQcmsPerf_Bgra : public GfxQcmsPerf_Base { + protected: + GfxQcmsPerf_Bgra() : GfxQcmsPerf_Base(QCMS_DATA_BGRA_8) {} +}; + +MOZ_GTEST_BENCH_F(GfxQcmsPerf_Rgb, TransformC, [this] { TransformPerf(); }); +MOZ_GTEST_BENCH_F(GfxQcmsPerf_Rgb, TransformPlatform, + [this] { TransformPlatformPerf(); }); +MOZ_GTEST_BENCH_F(GfxQcmsPerf_Rgba, TransformC, [this] { TransformPerf(); }); +MOZ_GTEST_BENCH_F(GfxQcmsPerf_Rgba, TransformPlatform, + [this] { TransformPlatformPerf(); }); +MOZ_GTEST_BENCH_F(GfxQcmsPerf_Bgra, TransformC, [this] { TransformPerf(); }); +MOZ_GTEST_BENCH_F(GfxQcmsPerf_Bgra, TransformPlatform, + [this] { TransformPlatformPerf(); }); diff --git a/gfx/tests/gtest/TestRect.cpp b/gfx/tests/gtest/TestRect.cpp new file mode 100644 index 0000000000..16e1fae407 --- /dev/null +++ b/gfx/tests/gtest/TestRect.cpp @@ -0,0 +1,682 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include <limits> + +#include "gtest/gtest.h" + +#include "gfxTypes.h" +#include "nsRect.h" +#include "nsRectAbsolute.h" +#include "gfxRect.h" +#include "mozilla/gfx/Rect.h" +#include "mozilla/gfx/RectAbsolute.h" +#include "mozilla/WritingModes.h" +#ifdef XP_WIN +# include <windows.h> +#endif + +using mozilla::CSSCoord; +using mozilla::CSSIntCoord; +using mozilla::CSSIntSize; +using mozilla::ScreenIntCoord; +using mozilla::gfx::IntRect; +using mozilla::gfx::IntRectAbsolute; + +static_assert(std::is_constructible_v<CSSIntSize, CSSIntCoord, CSSIntCoord>); +static_assert( + !std::is_constructible_v<CSSIntSize, ScreenIntCoord, ScreenIntCoord>); +static_assert(std::is_constructible_v<CSSIntSize, int, int>); +static_assert(!std::is_constructible_v<CSSIntSize, float, float>); + +static_assert(std::is_same_v<CSSIntCoord, decltype(CSSIntCoord() * 42)>); +static_assert(std::is_same_v<CSSCoord, decltype(CSSCoord() * 42)>); +static_assert(std::is_same_v<CSSCoord, decltype(CSSIntCoord() * 42.f)>); +static_assert(std::is_same_v<CSSCoord, decltype(CSSCoord() * 42.f)>); + +template <class RectType> +static bool TestConstructors() { + // Create a rectangle + RectType rect1(10, 20, 30, 40); + + // Make sure the rectangle was properly initialized + EXPECT_TRUE(rect1.IsEqualRect(10, 20, 30, 40) && rect1.IsEqualXY(10, 20) && + rect1.IsEqualSize(30, 40)) + << "[1] Make sure the rectangle was properly initialized with " + "constructor"; + + // Create a second rect using the copy constructor + RectType rect2(rect1); + + // Make sure the rectangle was properly initialized + EXPECT_TRUE(rect2.IsEqualEdges(rect1) && + rect2.IsEqualXY(rect1.X(), rect1.Y()) && + rect2.IsEqualSize(rect1.Width(), rect1.Height())) + << "[2] Make sure the rectangle was properly initialized with copy " + "constructor"; + + EXPECT_TRUE(!rect1.IsEmpty() && !rect1.IsZeroArea() && rect1.IsFinite() && + !rect2.IsEmpty() && !rect2.IsZeroArea() && rect2.IsFinite()) + << "[3] These rectangles are not empty and are finite"; + + rect1.SetRect(1, 2, 30, 40); + EXPECT_TRUE(rect1.X() == 1 && rect1.Y() == 2 && rect1.Width() == 30 && + rect1.Height() == 40 && rect1.XMost() == 31 && + rect1.YMost() == 42); + + rect1.SetRectX(11, 50); + EXPECT_TRUE(rect1.X() == 11 && rect1.Y() == 2 && rect1.Width() == 50 && + rect1.Height() == 40 && rect1.XMost() == 61 && + rect1.YMost() == 42); + + rect1.SetRectY(22, 60); + EXPECT_TRUE(rect1.X() == 11 && rect1.Y() == 22 && rect1.Width() == 50 && + rect1.Height() == 60 && rect1.XMost() == 61 && + rect1.YMost() == 82); + + rect1.SetBox(1, 2, 31, 42); + EXPECT_TRUE(rect1.X() == 1 && rect1.Y() == 2 && rect1.Width() == 30 && + rect1.Height() == 40 && rect1.XMost() == 31 && + rect1.YMost() == 42); + + rect1.SetBoxX(11, 61); + EXPECT_TRUE(rect1.X() == 11 && rect1.Y() == 2 && rect1.Width() == 50 && + rect1.Height() == 40 && rect1.XMost() == 61 && + rect1.YMost() == 42); + + rect1.SetBoxY(22, 82); + EXPECT_TRUE(rect1.X() == 11 && rect1.Y() == 22 && rect1.Width() == 50 && + rect1.Height() == 60 && rect1.XMost() == 61 && + rect1.YMost() == 82); + + rect1.SetRect(1, 2, 30, 40); + EXPECT_TRUE(rect1.X() == 1 && rect1.Y() == 2 && rect1.Width() == 30 && + rect1.Height() == 40 && rect1.XMost() == 31 && + rect1.YMost() == 42); + + rect1.MoveByX(10); + EXPECT_TRUE(rect1.X() == 11 && rect1.Y() == 2 && rect1.Width() == 30 && + rect1.Height() == 40 && rect1.XMost() == 41 && + rect1.YMost() == 42); + + rect1.MoveByY(20); + EXPECT_TRUE(rect1.X() == 11 && rect1.Y() == 22 && rect1.Width() == 30 && + rect1.Height() == 40 && rect1.XMost() == 41 && + rect1.YMost() == 62); + + return true; +} + +template <class RectType> +static bool TestEqualityOperator() { + RectType rect1(10, 20, 30, 40); + RectType rect2(rect1); + + // Test the equality operator + EXPECT_TRUE(rect1 == rect2) << "[1] Test the equality operator"; + + EXPECT_FALSE(!rect1.IsEqualInterior(rect2)) + << "[2] Test the inequality operator"; + + // Make sure that two empty rects are equal + rect1.SetEmpty(); + rect2.SetEmpty(); + EXPECT_TRUE(rect1 == rect2) << "[3] Make sure that two empty rects are equal"; + + return true; +} + +template <class RectType> +static bool TestContainment() { + RectType rect1(10, 10, 50, 50); + + // Test the point containment methods + // + + // Basic test of a point in the middle of the rect + EXPECT_TRUE(rect1.Contains(rect1.Center()) && + rect1.ContainsX(rect1.Center().x) && + rect1.ContainsY(rect1.Center().y)) + << "[1] Basic test of a point in the middle of the rect"; + + // Test against a point at the left/top edges + EXPECT_TRUE(rect1.Contains(rect1.X(), rect1.Y()) && + rect1.ContainsX(rect1.X()) && rect1.ContainsY(rect1.Y())) + << "[2] Test against a point at the left/top edges"; + + // Test against a point at the right/bottom extents + EXPECT_FALSE(rect1.Contains(rect1.XMost(), rect1.YMost()) || + rect1.ContainsX(rect1.XMost()) || rect1.ContainsY(rect1.YMost())) + << "[3] Test against a point at the right/bottom extents"; + + // Test the rect containment methods + // + RectType rect2(rect1); + + // Test against a rect that's the same as rect1 + EXPECT_FALSE(!rect1.Contains(rect2)) + << "[4] Test against a rect that's the same as rect1"; + + // Test against a rect whose left edge (only) is outside of rect1 + rect2.MoveByX(-1); + EXPECT_FALSE(rect1.Contains(rect2)) + << "[5] Test against a rect whose left edge (only) is outside of rect1"; + rect2.MoveByX(1); + + // Test against a rect whose top edge (only) is outside of rect1 + rect2.MoveByY(-1); + EXPECT_FALSE(rect1.Contains(rect2)) + << "[6] Test against a rect whose top edge (only) is outside of rect1"; + rect2.MoveByY(1); + + // Test against a rect whose right edge (only) is outside of rect1 + rect2.MoveByX(1); + EXPECT_FALSE(rect1.Contains(rect2)) + << "[7] Test against a rect whose right edge (only) is outside of rect1"; + rect2.MoveByX(-1); + + // Test against a rect whose bottom edge (only) is outside of rect1 + rect2.MoveByY(1); + EXPECT_FALSE(rect1.Contains(rect2)) + << "[8] Test against a rect whose bottom edge (only) is outside of rect1"; + rect2.MoveByY(-1); + + return true; +} + +// Test the method that returns a boolean result but doesn't return a +// a rectangle +template <class RectType> +static bool TestIntersects() { + RectType rect1(10, 10, 50, 50); + RectType rect2(rect1); + + // Test against a rect that's the same as rect1 + EXPECT_FALSE(!rect1.Intersects(rect2)) + << "[1] Test against a rect that's the same as rect1"; + + // Test against a rect that's enclosed by rect1 + rect2.Inflate(-1, -1); + EXPECT_FALSE(!rect1.Contains(rect2) || !rect1.Intersects(rect2)) + << "[2] Test against a rect that's enclosed by rect1"; + rect2.Inflate(1, 1); + + // Make sure inflate and deflate worked correctly + EXPECT_TRUE(rect1.IsEqualInterior(rect2)) + << "[3] Make sure inflate and deflate worked correctly"; + + // Test against a rect that overlaps the left edge of rect1 + rect2.MoveByX(-1); + EXPECT_FALSE(!rect1.Intersects(rect2)) + << "[4] Test against a rect that overlaps the left edge of rect1"; + rect2.MoveByX(1); + + // Test against a rect that's outside of rect1 on the left + rect2.MoveByX(-rect2.Width()); + EXPECT_FALSE(rect1.Intersects(rect2)) + << "[5] Test against a rect that's outside of rect1 on the left"; + rect2.MoveByX(rect2.Width()); + + // Test against a rect that overlaps the top edge of rect1 + rect2.MoveByY(-1); + EXPECT_FALSE(!rect1.Intersects(rect2)) + << "[6] Test against a rect that overlaps the top edge of rect1"; + rect2.MoveByY(1); + + // Test against a rect that's outside of rect1 on the top + rect2.MoveByY(-rect2.Height()); + EXPECT_FALSE(rect1.Intersects(rect2)) + << "[7] Test against a rect that's outside of rect1 on the top"; + rect2.MoveByY(rect2.Height()); + + // Test against a rect that overlaps the right edge of rect1 + rect2.MoveByX(1); + EXPECT_FALSE(!rect1.Intersects(rect2)) + << "[8] Test against a rect that overlaps the right edge of rect1"; + rect2.MoveByX(-1); + + // Test against a rect that's outside of rect1 on the right + rect2.MoveByX(rect2.Width()); + EXPECT_FALSE(rect1.Intersects(rect2)) + << "[9] Test against a rect that's outside of rect1 on the right"; + rect2.MoveByX(-rect2.Width()); + + // Test against a rect that overlaps the bottom edge of rect1 + rect2.MoveByY(1); + EXPECT_FALSE(!rect1.Intersects(rect2)) + << "[10] Test against a rect that overlaps the bottom edge of rect1"; + rect2.MoveByY(-1); + + // Test against a rect that's outside of rect1 on the bottom + rect2.MoveByY(rect2.Height()); + EXPECT_FALSE(rect1.Intersects(rect2)) + << "[11] Test against a rect that's outside of rect1 on the bottom"; + rect2.MoveByY(-rect2.Height()); + + return true; +} + +// Test the method that returns a boolean result and an intersection rect +template <class RectType> +static bool TestIntersection() { + RectType rect1(10, 10, 50, 50); + RectType rect2(rect1); + RectType dest; + + // Test against a rect that's the same as rect1 + EXPECT_FALSE(!dest.IntersectRect(rect1, rect2) || + !(dest.IsEqualInterior(rect1))) + << "[1] Test against a rect that's the same as rect1"; + + // Test against a rect that's enclosed by rect1 + rect2.Inflate(-1, -1); + EXPECT_FALSE(!dest.IntersectRect(rect1, rect2) || + !(dest.IsEqualInterior(rect2))) + << "[2] Test against a rect that's enclosed by rect1"; + rect2.Inflate(1, 1); + + // Test against a rect that overlaps the left edge of rect1 + rect2.MoveByX(-1); + EXPECT_FALSE(!dest.IntersectRect(rect1, rect2) || + !(dest.IsEqualInterior(RectType( + rect1.X(), rect1.Y(), rect1.Width() - 1, rect1.Height())))) + << "[3] Test against a rect that overlaps the left edge of rect1"; + rect2.MoveByX(1); + + // Test against a rect that's outside of rect1 on the left + rect2.MoveByX(-rect2.Width()); + EXPECT_FALSE(dest.IntersectRect(rect1, rect2)) + << "[4] Test against a rect that's outside of rect1 on the left"; + // Make sure an empty rect is returned + EXPECT_TRUE(dest.IsEmpty() && dest.IsZeroArea()) + << "[4] Make sure an empty rect is returned"; + EXPECT_TRUE(dest.IsFinite()) << "[4b] Should be finite"; + rect2.MoveByX(rect2.Width()); + + // Test against a rect that overlaps the top edge of rect1 + rect2.MoveByY(-1); + EXPECT_FALSE(!dest.IntersectRect(rect1, rect2) || + !(dest.IsEqualInterior(RectType( + rect1.X(), rect1.Y(), rect1.Width(), rect1.Height() - 1)))) + << "[5] Test against a rect that overlaps the top edge of rect1"; + EXPECT_TRUE(dest.IsFinite()) << "[5b] Should be finite"; + rect2.MoveByY(1); + + // Test against a rect that's outside of rect1 on the top + rect2.MoveByY(-rect2.Height()); + EXPECT_FALSE(dest.IntersectRect(rect1, rect2)) + << "[6] Test against a rect that's outside of rect1 on the top"; + // Make sure an empty rect is returned + EXPECT_TRUE(dest.IsEmpty() && dest.IsZeroArea()) + << "[6] Make sure an empty rect is returned"; + EXPECT_TRUE(dest.IsFinite()) << "[6b] Should be finite"; + rect2.MoveByY(rect2.Height()); + + // Test against a rect that overlaps the right edge of rect1 + rect2.MoveByX(1); + EXPECT_FALSE( + !dest.IntersectRect(rect1, rect2) || + !(dest.IsEqualInterior(RectType(rect1.X() + 1, rect1.Y(), + rect1.Width() - 1, rect1.Height())))) + << "[7] Test against a rect that overlaps the right edge of rect1"; + rect2.MoveByX(-1); + + // Test against a rect that's outside of rect1 on the right + rect2.MoveByX(rect2.Width()); + EXPECT_FALSE(dest.IntersectRect(rect1, rect2)) + << "[8] Test against a rect that's outside of rect1 on the right"; + // Make sure an empty rect is returned + EXPECT_TRUE(dest.IsEmpty() && dest.IsZeroArea()) + << "[8] Make sure an empty rect is returned"; + EXPECT_TRUE(dest.IsFinite()) << "[8b] Should be finite"; + rect2.MoveByX(-rect2.Width()); + + // Test against a rect that overlaps the bottom edge of rect1 + rect2.MoveByY(1); + EXPECT_FALSE( + !dest.IntersectRect(rect1, rect2) || + !(dest.IsEqualInterior(RectType(rect1.X(), rect1.Y() + 1, rect1.Width(), + rect1.Height() - 1)))) + << "[9] Test against a rect that overlaps the bottom edge of rect1"; + EXPECT_TRUE(dest.IsFinite()) << "[9b] Should be finite"; + rect2.MoveByY(-1); + + // Test against a rect that's outside of rect1 on the bottom + rect2.MoveByY(rect2.Height()); + EXPECT_FALSE(dest.IntersectRect(rect1, rect2)) + << "[10] Test against a rect that's outside of rect1 on the bottom"; + // Make sure an empty rect is returned + EXPECT_TRUE(dest.IsEmpty() && dest.IsZeroArea()) + << "[10] Make sure an empty rect is returned"; + EXPECT_TRUE(dest.IsFinite()) << "[10b] Should be finite"; + rect2.MoveByY(-rect2.Height()); + + // Test against a rect with zero width or height + rect1.SetRect(100, 100, 100, 100); + rect2.SetRect(150, 100, 0, 100); + EXPECT_TRUE(!dest.IntersectRect(rect1, rect2) && dest.IsEmpty() && + dest.IsZeroArea()) + << "[11] Intersection of rects with zero width or height should be empty"; + EXPECT_TRUE(dest.IsFinite()) << "[11b] Should be finite"; + + // Tests against a rect with negative width or height + // + + // Test against a rect with negative width + rect1.SetRect(100, 100, 100, 100); + rect2.SetRect(100, 100, -100, 100); + EXPECT_TRUE(!dest.IntersectRect(rect1, rect2) && dest.IsEmpty() && + dest.IsZeroArea()) + << "[12] Intersection of rects with negative width or height should be " + "empty"; + EXPECT_TRUE(dest.IsFinite()) << "[12b] Should be finite"; + + // Those two rects exactly overlap in some way... + // but we still want to return an empty rect + rect1.SetRect(100, 100, 100, 100); + rect2.SetRect(200, 200, -100, -100); + EXPECT_TRUE(!dest.IntersectRect(rect1, rect2) && dest.IsEmpty() && + dest.IsZeroArea()) + << "[13] Intersection of rects with negative width or height should be " + "empty"; + EXPECT_TRUE(dest.IsFinite()) << "[13b] Should be finite"; + + // Test against two identical rects with negative height + rect1.SetRect(100, 100, 100, -100); + rect2.SetRect(100, 100, 100, -100); + EXPECT_TRUE(!dest.IntersectRect(rect1, rect2) && dest.IsEmpty() && + dest.IsZeroArea()) + << "[14] Intersection of rects with negative width or height should be " + "empty"; + EXPECT_TRUE(dest.IsFinite()) << "[14b] Should be finite"; + + return true; +} + +template <class RectType> +static bool TestUnion() { + RectType rect1; + RectType rect2(10, 10, 50, 50); + RectType dest; + + // Check the case where the receiver is an empty rect + rect1.SetEmpty(); + dest.UnionRect(rect1, rect2); + EXPECT_TRUE(!dest.IsEmpty() && !dest.IsZeroArea() && + dest.IsEqualInterior(rect2)) + << "[1] Check the case where the receiver is an empty rect"; + EXPECT_TRUE(dest.IsFinite()) << "[1b] Should be finite"; + + // Check the case where the source rect is an empty rect + rect1 = rect2; + rect2.SetEmpty(); + dest.UnionRect(rect1, rect2); + EXPECT_TRUE(!dest.IsEmpty() && !dest.IsZeroArea() && + dest.IsEqualInterior(rect1)) + << "[2] Check the case where the source rect is an empty rect"; + EXPECT_TRUE(dest.IsFinite()) << "[2b] Should be finite"; + + // Test the case where both rects are empty + rect1.SetEmpty(); + rect2.SetEmpty(); + dest.UnionRect(rect1, rect2); + EXPECT_TRUE(dest.IsEmpty() && dest.IsZeroArea()) + << "[3] Test the case where both rects are empty"; + EXPECT_TRUE(dest.IsFinite()) << "[3b] Should be finite"; + + // Test union case where the two rects don't overlap at all + rect1.SetRect(10, 10, 50, 50); + rect2.SetRect(100, 100, 50, 50); + dest.UnionRect(rect1, rect2); + EXPECT_TRUE(!dest.IsEmpty() && !dest.IsZeroArea() && + (dest.IsEqualInterior(RectType(rect1.X(), rect1.Y(), + rect2.XMost() - rect1.X(), + rect2.YMost() - rect1.Y())))) + << "[4] Test union case where the two rects don't overlap at all"; + EXPECT_TRUE(dest.IsFinite()) << "[4b] Should be finite"; + + // Test union case where the two rects overlap + rect1.SetRect(30, 30, 50, 50); + rect2.SetRect(10, 10, 50, 50); + dest.UnionRect(rect1, rect2); + EXPECT_TRUE(!dest.IsEmpty() && !dest.IsZeroArea() && + (dest.IsEqualInterior(RectType(rect2.X(), rect2.Y(), + rect1.XMost() - rect2.X(), + rect1.YMost() - rect2.Y())))) + << "[5] Test union case where the two rects overlap"; + EXPECT_TRUE(dest.IsFinite()) << "[5b] Should be finite"; + + return true; +} + +template <class RectType> +static void TestUnionEmptyRects() { + RectType rect1(10, 10, 0, 50); + RectType rect2(5, 5, 40, 0); + EXPECT_TRUE(rect1.IsEmpty() && rect2.IsEmpty()); + + RectType dest = rect1.Union(rect2); + EXPECT_TRUE(dest.IsEmpty() && dest.IsEqualEdges(rect2)) + << "Test the case where both rects are empty, and the result is the " + "same value passing into Union()"; +} + +static bool TestFiniteGfx() { + float posInf = std::numeric_limits<float>::infinity(); + float negInf = -std::numeric_limits<float>::infinity(); + float justNaN = std::numeric_limits<float>::quiet_NaN(); + + gfxFloat values[4] = {5.0, 10.0, 15.0, 20.0}; + + // Try the "non-finite" values for x, y, width, height, one at a time + for (int i = 0; i < 4; i += 1) { + values[i] = posInf; + gfxRect rectPosInf(values[0], values[1], values[2], values[3]); + EXPECT_FALSE(rectPosInf.IsFinite()) + << "For +inf (" << values[0] << "," << values[1] << "," << values[2] + << "," << values[3] << ")"; + + values[i] = negInf; + gfxRect rectNegInf(values[0], values[1], values[2], values[3]); + EXPECT_FALSE(rectNegInf.IsFinite()) + << "For -inf (" << values[0] << "," << values[1] << "," << values[2] + << "," << values[3] << ")"; + + values[i] = justNaN; + gfxRect rectNaN(values[0], values[1], values[2], values[3]); + EXPECT_FALSE(rectNaN.IsFinite()) + << "For NaN (" << values[0] << "," << values[1] << "," << values[2] + << "," << values[3] << ")"; + + // Reset to a finite value... + values[i] = 5.0 * i; + } + + return true; +} + +// We want to test nsRect values that are still in range but where +// the implementation is at risk of overflowing +template <class RectType> +static bool TestBug1135677() { + RectType rect1(1073741344, 1073741344, 1073756696, 1073819936); + RectType rect2(1073741820, 1073741820, 14400, 77640); + RectType dest; + + dest = rect1.Intersect(rect2); + + EXPECT_TRUE(dest.IsEqualRect(1073741820, 1073741820, 14400, 77640)) + << "[1] Operation should not overflow internally."; + + return true; +} + +template <class RectType> +static bool TestSetWH() { + RectType rect(1, 2, 3, 4); + EXPECT_TRUE(rect.IsEqualRect(1, 2, 3, 4)); + rect.SetWidth(13); + EXPECT_TRUE(rect.IsEqualRect(1, 2, 13, 4)); + rect.SetHeight(14); + EXPECT_TRUE(rect.IsEqualRect(1, 2, 13, 14)); + rect.SizeTo(23, 24); + EXPECT_TRUE(rect.IsEqualRect(1, 2, 23, 24)); + return true; +} + +template <class RectType> +static bool TestSwap() { + RectType rect(1, 2, 3, 4); + EXPECT_TRUE(rect.IsEqualRect(1, 2, 3, 4)); + rect.Swap(); + EXPECT_TRUE(rect.IsEqualRect(2, 1, 4, 3)); + return true; +} + +static void TestIntersectionLogicalHelper(nscoord x1, nscoord y1, nscoord w1, + nscoord h1, nscoord x2, nscoord y2, + nscoord w2, nscoord h2, nscoord xR, + nscoord yR, nscoord wR, nscoord hR, + bool isNonEmpty) { + nsRect rect1(x1, y1, w1, h1); + nsRect rect2(x2, y2, w2, h2); + nsRect rectDebug; + EXPECT_TRUE(isNonEmpty == rectDebug.IntersectRect(rect1, rect2)); + EXPECT_TRUE(rectDebug.IsEqualEdges(nsRect(xR, yR, wR, hR))); + + mozilla::LogicalRect r1(mozilla::WritingMode(), rect1.X(), rect1.Y(), + rect1.Width(), rect1.Height()); + mozilla::LogicalRect r2(mozilla::WritingMode(), rect2.X(), rect2.Y(), + rect2.Width(), rect2.Height()); + EXPECT_TRUE(isNonEmpty == r1.IntersectRect(r1, r2)); + EXPECT_TRUE(rectDebug.IsEqualEdges(nsRect( + r1.IStart(mozilla::WritingMode()), r1.BStart(mozilla::WritingMode()), + r1.ISize(mozilla::WritingMode()), r1.BSize(mozilla::WritingMode())))); + + mozilla::LogicalRect r3(mozilla::WritingMode(), rect1.X(), rect1.Y(), + rect1.Width(), rect1.Height()); + mozilla::LogicalRect r4(mozilla::WritingMode(), rect2.X(), rect2.Y(), + rect2.Width(), rect2.Height()); + EXPECT_TRUE(isNonEmpty == r4.IntersectRect(r3, r4)); + EXPECT_TRUE(rectDebug.IsEqualEdges(nsRect( + r4.IStart(mozilla::WritingMode()), r4.BStart(mozilla::WritingMode()), + r4.ISize(mozilla::WritingMode()), r4.BSize(mozilla::WritingMode())))); + + mozilla::LogicalRect r5(mozilla::WritingMode(), rect1.X(), rect1.Y(), + rect1.Width(), rect1.Height()); + mozilla::LogicalRect r6(mozilla::WritingMode(), rect2.X(), rect2.Y(), + rect2.Width(), rect2.Height()); + mozilla::LogicalRect r7(mozilla::WritingMode(), 0, 0, 1, 1); + EXPECT_TRUE(isNonEmpty == r7.IntersectRect(r5, r6)); + EXPECT_TRUE(rectDebug.IsEqualEdges(nsRect( + r7.IStart(mozilla::WritingMode()), r7.BStart(mozilla::WritingMode()), + r7.ISize(mozilla::WritingMode()), r7.BSize(mozilla::WritingMode())))); +} + +static void TestIntersectionLogical(nscoord x1, nscoord y1, nscoord w1, + nscoord h1, nscoord x2, nscoord y2, + nscoord w2, nscoord h2, nscoord xR, + nscoord yR, nscoord wR, nscoord hR, + bool isNonEmpty) { + TestIntersectionLogicalHelper(x1, y1, w1, h1, x2, y2, w2, h2, xR, yR, wR, hR, + isNonEmpty); + TestIntersectionLogicalHelper(x2, y2, w2, h2, x1, y1, w1, h1, xR, yR, wR, hR, + isNonEmpty); +} + +TEST(Gfx, Logical) +{ + TestIntersectionLogical(578, 0, 2650, 1152, 1036, 0, 2312, 1, 1036, 0, 2192, + 1, true); + TestIntersectionLogical(0, 0, 1000, 1000, 500, 500, 1000, 1000, 500, 500, 500, + 500, true); + TestIntersectionLogical(100, 200, 300, 400, 50, 250, 100, 100, 100, 250, 50, + 100, true); + TestIntersectionLogical(0, 100, 200, 300, 300, 100, 100, 300, 300, 100, 0, 0, + false); +} + +TEST(Gfx, nsRect) +{ + TestConstructors<nsRect>(); + TestEqualityOperator<nsRect>(); + TestContainment<nsRect>(); + TestIntersects<nsRect>(); + TestIntersection<nsRect>(); + TestUnion<nsRect>(); + TestUnionEmptyRects<nsRect>(); + TestBug1135677<nsRect>(); + TestSetWH<nsRect>(); + TestSwap<nsRect>(); +} + +TEST(Gfx, nsIntRect) +{ + TestConstructors<nsIntRect>(); + TestEqualityOperator<nsIntRect>(); + TestContainment<nsIntRect>(); + TestIntersects<nsIntRect>(); + TestIntersection<nsIntRect>(); + TestUnion<nsIntRect>(); + TestUnionEmptyRects<nsIntRect>(); + TestBug1135677<nsIntRect>(); + TestSetWH<nsIntRect>(); + TestSwap<nsIntRect>(); +} + +TEST(Gfx, gfxRect) +{ + TestConstructors<gfxRect>(); + // Skip TestEqualityOperator<gfxRect>(); as gfxRect::operator== is private + TestContainment<gfxRect>(); + TestIntersects<gfxRect>(); + TestIntersection<gfxRect>(); + TestUnion<gfxRect>(); + TestUnionEmptyRects<gfxRect>(); + TestBug1135677<gfxRect>(); + TestFiniteGfx(); + TestSetWH<gfxRect>(); + TestSwap<gfxRect>(); +} + +TEST(Gfx, nsRectAbsolute) +{ TestUnionEmptyRects<nsRectAbsolute>(); } + +TEST(Gfx, IntRectAbsolute) +{ TestUnionEmptyRects<IntRectAbsolute>(); } + +static void TestMoveInsideAndClamp(IntRect aSrc, IntRect aTarget, + IntRect aExpected) { + // Test the implementation in BaseRect (x/y/width/height representation) + IntRect result = aSrc.MoveInsideAndClamp(aTarget); + EXPECT_TRUE(result.IsEqualEdges(aExpected)) + << "Source " << aSrc << " Target " << aTarget << " Expected " << aExpected + << " Actual " << result; + + // Also test the implementation in RectAbsolute (left/top/right/bottom + // representation) + IntRectAbsolute absSrc = IntRectAbsolute::FromRect(aSrc); + IntRectAbsolute absTarget = IntRectAbsolute::FromRect(aTarget); + IntRectAbsolute absExpected = IntRectAbsolute::FromRect(aExpected); + + IntRectAbsolute absResult = absSrc.MoveInsideAndClamp(absTarget); + EXPECT_TRUE(absResult.IsEqualEdges(absExpected)) + << "AbsSource " << absSrc << " AbsTarget " << absTarget << " AbsExpected " + << absExpected << " AbsActual " << absResult; +} + +TEST(Gfx, MoveInsideAndClamp) +{ + TestMoveInsideAndClamp(IntRect(0, 0, 10, 10), IntRect(1, -1, 10, 10), + IntRect(1, -1, 10, 10)); + TestMoveInsideAndClamp(IntRect(0, 0, 10, 10), IntRect(-1, -1, 12, 5), + IntRect(0, -1, 10, 5)); + TestMoveInsideAndClamp(IntRect(0, 0, 10, 10), IntRect(10, 11, 10, 0), + IntRect(10, 11, 10, 0)); + TestMoveInsideAndClamp(IntRect(0, 0, 10, 10), IntRect(-10, -1, 10, 0), + IntRect(-10, -1, 10, 0)); + TestMoveInsideAndClamp(IntRect(0, 0, 0, 0), IntRect(10, -10, 10, 10), + IntRect(10, 0, 0, 0)); +} diff --git a/gfx/tests/gtest/TestRegion.cpp b/gfx/tests/gtest/TestRegion.cpp new file mode 100644 index 0000000000..87a699ad78 --- /dev/null +++ b/gfx/tests/gtest/TestRegion.cpp @@ -0,0 +1,1350 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <algorithm> + +#include "gtest/gtest.h" +#include "nsRegion.h" +#include "RegionBuilder.h" +#include "mozilla/UniquePtr.h" + +using namespace mozilla::gfx; + +// #define REGION_RANDOM_STRESS_TESTS + +class TestLargestRegion { + public: + static void TestSingleRect(nsRect r) { + nsRegion region(r); + EXPECT_TRUE(region.GetLargestRectangle().IsEqualInterior(r)); + } + // Construct a rectangle, remove part of it, then check the remainder + static void TestNonRectangular() { + nsRegion r(nsRect(0, 0, 30, 30)); + + const int nTests = 19; + struct { + nsRect rect; + int64_t expectedArea; + } tests[nTests] = {// Remove a 20x10 chunk from the square + {nsRect(0, 0, 20, 10), 600}, + {nsRect(10, 0, 20, 10), 600}, + {nsRect(10, 20, 20, 10), 600}, + {nsRect(0, 20, 20, 10), 600}, + // Remove a 10x20 chunk from the square + {nsRect(0, 0, 10, 20), 600}, + {nsRect(20, 0, 10, 20), 600}, + {nsRect(20, 10, 10, 20), 600}, + {nsRect(0, 10, 10, 20), 600}, + // Remove the center 10x10 + {nsRect(10, 10, 10, 10), 300}, + // Remove the middle column + {nsRect(10, 0, 10, 30), 300}, + // Remove the middle row + {nsRect(0, 10, 30, 10), 300}, + // Remove the corners 10x10 + {nsRect(0, 0, 10, 10), 600}, + {nsRect(20, 20, 10, 10), 600}, + {nsRect(20, 0, 10, 10), 600}, + {nsRect(0, 20, 10, 10), 600}, + // Remove the corners 20x20 + {nsRect(0, 0, 20, 20), 300}, + {nsRect(10, 10, 20, 20), 300}, + {nsRect(10, 0, 20, 20), 300}, + {nsRect(0, 10, 20, 20), 300}}; + + for (int32_t i = 0; i < nTests; i++) { + nsRegion r2; + r2.Sub(r, tests[i].rect); + + EXPECT_TRUE(r2.IsComplex()) << "nsRegion code got unexpectedly smarter!"; + + nsRect largest = r2.GetLargestRectangle(); + EXPECT_TRUE(largest.Width() * largest.Height() == tests[i].expectedArea) + << "Did not successfully find largest rectangle in non-rectangular " + "region on iteration " + << i; + } + } + static void TwoRectTest() { + nsRegion r(nsRect(0, 0, 100, 100)); + const int nTests = 4; + struct { + nsRect rect1, rect2; + int64_t expectedArea; + } tests[nTests] = { + {nsRect(0, 0, 75, 40), nsRect(0, 60, 75, 40), 2500}, + {nsRect(25, 0, 75, 40), nsRect(25, 60, 75, 40), 2500}, + {nsRect(25, 0, 75, 40), nsRect(0, 60, 75, 40), 2000}, + {nsRect(0, 0, 75, 40), nsRect(25, 60, 75, 40), 2000}, + }; + for (int32_t i = 0; i < nTests; i++) { + nsRegion r2; + + r2.Sub(r, tests[i].rect1); + r2.Sub(r2, tests[i].rect2); + + EXPECT_TRUE(r2.IsComplex()) << "nsRegion code got unexpectedly smarter!"; + + nsRect largest = r2.GetLargestRectangle(); + EXPECT_TRUE(largest.Width() * largest.Height() == tests[i].expectedArea) + << "Did not successfully find largest rectangle in two-rect-subtract " + "region on iteration " + << i; + } + } + static void TestContainsSpecifiedRect() { + nsRegion r(nsRect(0, 0, 100, 100)); + r.Or(r, nsRect(0, 300, 50, 50)); + EXPECT_TRUE(r.GetLargestRectangle(nsRect(0, 300, 10, 10)) + .IsEqualInterior(nsRect(0, 300, 50, 50))) + << "Chose wrong rectangle"; + } + static void TestContainsSpecifiedOverflowingRect() { + nsRegion r(nsRect(0, 0, 100, 100)); + r.Or(r, nsRect(0, 300, 50, 50)); + EXPECT_TRUE(r.GetLargestRectangle(nsRect(0, 290, 10, 20)) + .IsEqualInterior(nsRect(0, 300, 50, 50))) + << "Chose wrong rectangle"; + } +}; + +TEST(Gfx, RegionSingleRect) +{ + TestLargestRegion::TestSingleRect(nsRect(0, 52, 720, 480)); + TestLargestRegion::TestSingleRect(nsRect(-20, 40, 50, 20)); + TestLargestRegion::TestSingleRect(nsRect(-20, 40, 10, 8)); + TestLargestRegion::TestSingleRect(nsRect(-20, -40, 10, 8)); + TestLargestRegion::TestSingleRect(nsRect(-10, -10, 20, 20)); +} + +TEST(Gfx, RegionNonRectangular) +{ TestLargestRegion::TestNonRectangular(); } + +TEST(Gfx, RegionTwoRectTest) +{ TestLargestRegion::TwoRectTest(); } + +TEST(Gfx, RegionContainsSpecifiedRect) +{ TestLargestRegion::TestContainsSpecifiedRect(); } + +TEST(Gfx, RegionTestContainsSpecifiedOverflowingRect) +{ TestLargestRegion::TestContainsSpecifiedOverflowingRect(); } + +TEST(Gfx, RegionScaleToInside) +{ + { // no rectangles + nsRegion r; + + nsIntRegion scaled = r.ScaleToInsidePixels(1, 1, 60); + nsIntRegion result; + + EXPECT_TRUE(result.IsEqual(scaled)) << "scaled result incorrect"; + } + + { // one rectangle + nsRegion r(nsRect(0, 44760, 19096, 264)); + + nsIntRegion scaled = r.ScaleToInsidePixels(1, 1, 60); + nsIntRegion result(mozilla::gfx::IntRect(0, 746, 318, 4)); + + EXPECT_TRUE(result.IsEqual(scaled)) << "scaled result incorrect"; + } + + { // the first rectangle gets adjusted + nsRegion r(nsRect(0, 44760, 19096, 264)); + r.Or(r, nsRect(0, 45024, 19360, 1056)); + + nsIntRegion scaled = r.ScaleToInsidePixels(1, 1, 60); + nsIntRegion result(mozilla::gfx::IntRect(0, 746, 318, 5)); + result.Or(result, mozilla::gfx::IntRect(0, 751, 322, 17)); + + EXPECT_TRUE(result.IsEqual(scaled)) << "scaled result incorrect"; + } + + { // the second rectangle gets adjusted + nsRegion r(nsRect(0, 44760, 19360, 264)); + r.Or(r, nsRect(0, 45024, 19096, 1056)); + + nsIntRegion scaled = r.ScaleToInsidePixels(1, 1, 60); + nsIntRegion result(mozilla::gfx::IntRect(0, 746, 322, 4)); + result.Or(result, mozilla::gfx::IntRect(0, 750, 318, 18)); + + EXPECT_TRUE(result.IsEqual(scaled)) << "scaled result incorrect"; + } +} + +TEST(Gfx, RegionIsEqual) +{ + { + nsRegion r(nsRect(0, 0, 50, 50)); + EXPECT_FALSE(nsRegion().IsEqual(r)); + } + { + nsRegion r1(nsRect(0, 0, 50, 50)); + nsRegion r2(nsRect(0, 0, 50, 50)); + EXPECT_TRUE(r1.IsEqual(r2)); + } + { + nsRegion r1(nsRect(0, 0, 50, 50)); + nsRegion r2(nsRect(0, 0, 60, 50)); + EXPECT_FALSE(r1.IsEqual(r2)); + } + { + nsRegion r1(nsRect(0, 0, 50, 50)); + r1.OrWith(nsRect(0, 60, 50, 50)); + nsRegion r2(nsRect(0, 0, 50, 50)); + r2.OrWith(nsRect(0, 60, 50, 50)); + EXPECT_TRUE(r1.IsEqual(r2)); + } + { + nsRegion r1(nsRect(0, 0, 50, 50)); + r1.OrWith(nsRect(0, 60, 50, 50)); + nsRegion r2(nsRect(0, 0, 50, 50)); + r2.OrWith(nsRect(0, 70, 50, 50)); + EXPECT_FALSE(r1.IsEqual(r2)); + } + { + nsRegion r1(nsRect(0, 0, 50, 50)); + r1.OrWith(nsRect(0, 60, 50, 50)); + r1.OrWith(nsRect(100, 60, 50, 50)); + nsRegion r2(nsRect(0, 0, 50, 50)); + r2.OrWith(nsRect(0, 60, 50, 50)); + EXPECT_FALSE(r1.IsEqual(r2)); + } +} + +TEST(Gfx, RegionOrWith) +{ + PR_Sleep(PR_SecondsToInterval(10)); + { + nsRegion r(nsRect(11840, 11840, 4640, -10880)); + r.OrWith(nsRect(160, 160, 7720, 880)); + } + { + nsRegion r(nsRect(79, 31, 75, 12)); + r.OrWith(nsRect(22, 43, 132, 5)); + r.OrWith(nsRect(22, 48, 125, 3)); + r.OrWith(nsRect(22, 51, 96, 20)); + r.OrWith(nsRect(34, 71, 1, 14)); + r.OrWith(nsRect(26, 85, 53, 1)); + r.OrWith(nsRect(26, 86, 53, 4)); + r.OrWith(nsRect(96, 86, 30, 4)); + r.OrWith(nsRect(34, 90, 1, 2)); + r.OrWith(nsRect(96, 90, 30, 2)); + r.OrWith(nsRect(34, 92, 1, 3)); + r.OrWith(nsRect(49, 92, 34, 3)); + r.OrWith(nsRect(96, 92, 30, 3)); + r.OrWith(nsRect(34, 95, 1, 17)); + r.OrWith(nsRect(49, 95, 77, 17)); + r.OrWith(nsRect(34, 112, 1, 12)); + r.OrWith(nsRect(75, 112, 51, 12)); + r.OrWith(nsRect(34, 124, 1, 10)); + r.OrWith(nsRect(75, 124, 44, 10)); + r.OrWith(nsRect(34, 134, 1, 19)); + r.OrWith(nsRect(22, 17, 96, 27)); + } + { + nsRegion r(nsRect(0, 8, 257, 32)); + r.OrWith(nsRect(3702, 8, 138, 32)); + r.OrWith(nsRect(0, 40, 225, 1)); + r.OrWith(nsRect(3702, 40, 138, 1)); + r.OrWith(nsRect(0, 41, 101, 40)); + r.OrWith(nsRect(69, 41, 32, 40)); + } + { + nsRegion r(nsRect(79, 56, 8, 32)); + r.OrWith(nsRect(5, 94, 23, 81)); + r.OrWith(nsRect(56, 29, 91, 81)); + } + { + nsRegion r(nsRect(0, 82, 3840, 2046)); + r.OrWith(nsRect(0, 0, 3840, 82)); + } + { + nsRegion r(nsRect(2, 5, 600, 28)); + r.OrWith(nsRect(2, 82, 600, 19)); + r.OrWith(nsRect(2, 33, 600, 49)); + } + { + nsRegion r(nsRect(3823, 0, 17, 17)); + r.OrWith(nsRect(3823, 2029, 17, 17)); + r.OrWith(nsRect(3823, 0, 17, 2046)); + } + { + nsRegion r(nsRect(1036, 4, 32, 21)); + r.OrWith(nsRect(1070, 4, 66, 21)); + r.OrWith(nsRect(40, 5, 0, 33)); + } + { + nsRegion r(nsRect(0, 0, 1024, 1152)); + r.OrWith(nsRect(-335802, -1073741824, 1318851, 1860043520)); + } + { + nsRegion r(nsRect(0, 0, 800, 1000)); + r.OrWith(nsRect(0, 0, 536870912, 1073741824)); + } + { + nsRegion r(nsRect(53, 2, 52, 3)); + r.OrWith(nsRect(45, 5, 60, 16)); + r.OrWith(nsRect(16, 21, 8, 1)); + r.OrWith(nsRect(45, 21, 12, 1)); + r.OrWith(nsRect(16, 22, 8, 5)); + r.OrWith(nsRect(33, 22, 52, 5)); + r.OrWith(nsRect(16, 27, 8, 7)); + r.OrWith(nsRect(33, 27, 66, 7)); + r.OrWith(nsRect(0, 34, 99, 1)); + r.OrWith(nsRect(0, 35, 159, 27)); + r.OrWith(nsRect(0, 62, 122, 3)); + r.OrWith(nsRect(0, 65, 85, 11)); + r.OrWith(nsRect(91, 65, 97, 11)); + r.OrWith(nsRect(11, 76, 74, 2)); + r.OrWith(nsRect(91, 76, 97, 2)); + r.OrWith(nsRect(11, 78, 74, 12)); + r.OrWith(nsRect(11, 90, 13, 3)); + r.OrWith(nsRect(33, 90, 108, 3)); + r.OrWith(nsRect(16, 93, 8, 22)); + r.OrWith(nsRect(33, 93, 108, 22)); + r.OrWith(nsRect(16, 115, 8, 1)); + r.OrWith(nsRect(58, 115, 83, 1)); + r.OrWith(nsRect(58, 116, 83, 25)); + r.OrWith(nsRect(59, 37, 88, 92)); + } +#ifdef REGION_RANDOM_STRESS_TESTS + const uint32_t TestIterations = 100000; + const uint32_t RectsPerTest = 100; + + nsRect rects[RectsPerTest]; + + for (uint32_t i = 0; i < TestIterations; i++) { + nsRegion r; + for (uint32_t n = 0; n < RectsPerTest; n++) { + rects[n].SetRect(rand() % 100, rand() % 100, rand() % 99 + 1, + rand() % 99 + 1); + } + r.SetEmpty(); + for (uint32_t n = 0; n < RectsPerTest; n++) { + r.OrWith(rects[n]); + } + for (uint32_t n = 0; n < RectsPerTest; n++) { + EXPECT_TRUE(r.Contains(rects[n])); + } + } +#endif +} + +TEST(Gfx, RegionSubWith) +{ + { + nsRegion r1(nsRect(0, 0, 100, 50)); + r1.OrWith(nsRect(50, 50, 50, 50)); + nsRegion r2(nsRect(0, 0, 100, 50)); + r2.OrWith(nsRect(50, 50, 50, 50)); + r1.SubWith(r2); + EXPECT_FALSE(r1.Contains(1, 1)); + } + { + nsRegion r1(nsRect(0, 0, 800, 1000)); + nsRegion r2(nsRect(8, 108, 22, 20)); + r2.OrWith(nsRect(91, 138, 17, 18)); + r1.SubWith(r2); + EXPECT_TRUE(r1.Contains(400, 130)); + } + { + nsRegion r1(nsRect(392, 2, 28, 7)); + r1.OrWith(nsRect(115, 9, 305, 16)); + r1.OrWith(nsRect(392, 25, 28, 5)); + r1.OrWith(nsRect(0, 32, 1280, 41)); + nsRegion r2(nsRect(0, 0, 1280, 9)); + r2.OrWith(nsRect(0, 9, 115, 16)); + r2.OrWith(nsRect(331, 9, 949, 16)); + r2.OrWith(nsRect(0, 25, 1280, 7)); + r2.OrWith(nsRect(331, 32, 124, 1)); + r1.SubWith(r2); + EXPECT_FALSE(r1.Contains(350, 15)); + } + { + nsRegion r1(nsRect(552, 0, 2, 2)); + r1.OrWith(nsRect(362, 2, 222, 28)); + r1.OrWith(nsRect(552, 30, 2, 2)); + r1.OrWith(nsRect(0, 32, 1280, 41)); + nsRegion r2(nsRect(512, 0, 146, 9)); + r2.OrWith(nsRect(340, 9, 318, 16)); + r2.OrWith(nsRect(512, 25, 146, 8)); + r1.SubWith(r2); + EXPECT_FALSE(r1.Contains(350, 15)); + } + { + nsRegion r(nsRect(0, 0, 229380, 6780)); + r.OrWith(nsRect(76800, 6780, 76800, 4440)); + r.OrWith(nsRect(76800, 11220, 44082, 1800)); + r.OrWith(nsRect(122682, 11220, 30918, 1800)); + r.OrWith(nsRect(76800, 13020, 76800, 2340)); + r.OrWith(nsRect(85020, 15360, 59340, 75520)); + r.OrWith(nsRect(85020, 90880, 38622, 11332)); + r.OrWith(nsRect(143789, 90880, 571, 11332)); + r.OrWith(nsRect(85020, 102212, 59340, 960)); + r.OrWith(nsRect(85020, 103172, 38622, 1560)); + r.OrWith(nsRect(143789, 103172, 571, 1560)); + r.OrWith(nsRect(85020, 104732, 59340, 12292)); + r.OrWith(nsRect(85020, 117024, 38622, 1560)); + r.OrWith(nsRect(143789, 117024, 571, 1560)); + r.OrWith(nsRect(85020, 118584, 59340, 11976)); + r.SubWith(nsRect(123642, 89320, 20147, 1560)); + } + { + nsRegion r(nsRect(0, 0, 9480, 12900)); + r.OrWith(nsRect(0, 12900, 8460, 1020)); + r.SubWith(nsRect(8460, 0, 1020, 12900)); + } + { + nsRegion r1(nsRect(99, 1, 51, 2)); + r1.OrWith(nsRect(85, 3, 65, 1)); + r1.OrWith(nsRect(10, 4, 66, 5)); + r1.OrWith(nsRect(85, 4, 37, 5)); + r1.OrWith(nsRect(10, 9, 112, 3)); + r1.OrWith(nsRect(1, 12, 121, 1)); + r1.OrWith(nsRect(1, 13, 139, 3)); + r1.OrWith(nsRect(0, 16, 140, 3)); + r1.OrWith(nsRect(0, 19, 146, 3)); + r1.OrWith(nsRect(0, 22, 149, 2)); + r1.OrWith(nsRect(0, 24, 154, 2)); + r1.OrWith(nsRect(0, 26, 160, 23)); + r1.OrWith(nsRect(0, 49, 162, 31)); + r1.OrWith(nsRect(0, 80, 171, 19)); + r1.OrWith(nsRect(0, 99, 173, 11)); + r1.OrWith(nsRect(2, 110, 171, 6)); + r1.OrWith(nsRect(6, 116, 165, 5)); + r1.OrWith(nsRect(8, 121, 163, 1)); + r1.OrWith(nsRect(13, 122, 158, 11)); + r1.OrWith(nsRect(14, 133, 157, 23)); + r1.OrWith(nsRect(29, 156, 142, 10)); + r1.OrWith(nsRect(37, 166, 134, 6)); + r1.OrWith(nsRect(55, 172, 4, 4)); + r1.OrWith(nsRect(83, 172, 88, 4)); + r1.OrWith(nsRect(55, 176, 4, 2)); + r1.OrWith(nsRect(89, 176, 6, 2)); + r1.OrWith(nsRect(89, 178, 6, 4)); + nsRegion r2(nsRect(63, 11, 39, 11)); + r2.OrWith(nsRect(63, 22, 99, 16)); + r2.OrWith(nsRect(37, 38, 125, 61)); + r2.OrWith(nsRect(45, 99, 117, 8)); + r2.OrWith(nsRect(47, 107, 115, 7)); + r2.OrWith(nsRect(47, 114, 66, 1)); + r2.OrWith(nsRect(49, 115, 64, 2)); + r2.OrWith(nsRect(49, 117, 54, 30)); + r1.SubWith(r2); + } + { + nsRegion r1(nsRect(95, 2, 47, 1)); + r1.OrWith(nsRect(62, 3, 80, 2)); + r1.OrWith(nsRect(1, 5, 18, 3)); + r1.OrWith(nsRect(48, 5, 94, 3)); + r1.OrWith(nsRect(1, 8, 18, 3)); + r1.OrWith(nsRect(23, 8, 119, 3)); + r1.OrWith(nsRect(1, 11, 172, 9)); + r1.OrWith(nsRect(1, 20, 18, 8)); + r1.OrWith(nsRect(20, 20, 153, 8)); + r1.OrWith(nsRect(1, 28, 172, 13)); + r1.OrWith(nsRect(1, 41, 164, 1)); + r1.OrWith(nsRect(1, 42, 168, 1)); + r1.OrWith(nsRect(0, 43, 169, 15)); + r1.OrWith(nsRect(1, 58, 168, 26)); + r1.OrWith(nsRect(1, 84, 162, 2)); + r1.OrWith(nsRect(1, 86, 165, 23)); + r1.OrWith(nsRect(1, 109, 162, 23)); + r1.OrWith(nsRect(1, 132, 152, 4)); + r1.OrWith(nsRect(1, 136, 150, 12)); + r1.OrWith(nsRect(12, 148, 139, 4)); + r1.OrWith(nsRect(12, 152, 113, 2)); + r1.OrWith(nsRect(14, 154, 31, 3)); + r1.OrWith(nsRect(82, 154, 43, 3)); + r1.OrWith(nsRect(17, 157, 13, 19)); + r1.OrWith(nsRect(82, 157, 43, 19)); + r1.OrWith(nsRect(17, 176, 13, 16)); + nsRegion r2(nsRect(97, 9, 6, 10)); + r2.OrWith(nsRect(71, 19, 32, 2)); + r2.OrWith(nsRect(20, 21, 83, 2)); + r2.OrWith(nsRect(2, 23, 101, 9)); + r2.OrWith(nsRect(2, 32, 98, 1)); + r2.OrWith(nsRect(2, 33, 104, 5)); + r2.OrWith(nsRect(2, 38, 118, 2)); + r2.OrWith(nsRect(15, 40, 9, 11)); + r2.OrWith(nsRect(36, 40, 84, 11)); + r2.OrWith(nsRect(4, 51, 116, 33)); + r2.OrWith(nsRect(4, 84, 159, 8)); + r2.OrWith(nsRect(4, 92, 116, 13)); + r2.OrWith(nsRect(15, 105, 9, 7)); + r2.OrWith(nsRect(36, 105, 84, 7)); + r2.OrWith(nsRect(36, 112, 84, 22)); + r2.OrWith(nsRect(71, 134, 39, 46)); + r1.SubWith(r2); + } +#ifdef REGION_RANDOM_STRESS_TESTS + const uint32_t TestIterations = 100000; + const uint32_t RectsPerTest = 100; + const uint32_t SubRectsPerTest = 10; + + nsRect rects[RectsPerTest]; + nsRect subRects[SubRectsPerTest]; + + for (uint32_t i = 0; i < TestIterations; i++) { + nsRegion r; + for (uint32_t n = 0; n < RectsPerTest; n++) { + rects[n].SetRect(rand() % 100, rand() % 100, rand() % 99 + 1, + rand() % 99 + 1); + } + r.SetEmpty(); + for (uint32_t n = 0; n < RectsPerTest; n++) { + r.OrWith(rects[n]); + } + for (uint32_t n = 0; n < RectsPerTest; n++) { + EXPECT_TRUE(r.Contains(rects[n])); + } + for (uint32_t n = 0; n < SubRectsPerTest; n++) { + subRects[n].SetRect(rand() % 100, rand() % 100, rand() % 99 + 1, + rand() % 99 + 1); + } + for (uint32_t n = 0; n < SubRectsPerTest; n++) { + r.SubWith(subRects[n]); + } + for (uint32_t n = 0; n < SubRectsPerTest; n++) { + EXPECT_FALSE(r.Contains(subRects[n].x, subRects[n].y)); + EXPECT_FALSE(r.Contains(subRects[n].x, subRects[n].YMost() - 1)); + EXPECT_FALSE( + r.Contains(subRects[n].XMost() - 1, subRects[n].YMost() - 1)); + EXPECT_FALSE(r.Contains(subRects[n].XMost() - 1, subRects[n].Y())); + } + } + for (uint32_t i = 0; i < TestIterations; i++) { + nsRegion r; + for (uint32_t n = 0; n < RectsPerTest; n++) { + rects[n].SetRect(rand() % 100, rand() % 100, rand() % 99 + 1, + rand() % 99 + 1); + } + r.SetEmpty(); + for (uint32_t n = 0; n < RectsPerTest; n++) { + r.OrWith(rects[n]); + } + for (uint32_t n = 0; n < RectsPerTest; n++) { + EXPECT_TRUE(r.Contains(rects[n])); + } + for (uint32_t n = 0; n < SubRectsPerTest; n++) { + subRects[n].SetRect(rand() % 100, rand() % 100, rand() % 99 + 1, + rand() % 99 + 1); + } + nsRegion r2; + for (uint32_t n = 0; n < SubRectsPerTest; n++) { + r2.OrWith(subRects[n]); + } + r.SubWith(r2); + + for (uint32_t n = 0; n < SubRectsPerTest; n++) { + EXPECT_FALSE(r.Contains(subRects[n].x, subRects[n].y)); + EXPECT_FALSE(r.Contains(subRects[n].x, subRects[n].YMost() - 1)); + EXPECT_FALSE( + r.Contains(subRects[n].XMost() - 1, subRects[n].YMost() - 1)); + EXPECT_FALSE(r.Contains(subRects[n].XMost() - 1, subRects[n].Y())); + } + } + for (uint32_t i = 0; i < TestIterations; i++) { + nsRegion r(nsRect(-1, -1, 202, 202)); + for (uint32_t n = 0; n < SubRectsPerTest; n++) { + subRects[n].SetRect(rand() % 100, rand() % 100, rand() % 99 + 1, + rand() % 99 + 1); + r.SubWith(subRects[n]); + } + for (uint32_t n = 0; n < SubRectsPerTest; n++) { + EXPECT_FALSE(r.Contains(subRects[n].x, subRects[n].y)); + EXPECT_FALSE(r.Contains(subRects[n].x, subRects[n].YMost() - 1)); + EXPECT_FALSE( + r.Contains(subRects[n].XMost() - 1, subRects[n].YMost() - 1)); + EXPECT_FALSE(r.Contains(subRects[n].XMost() - 1, subRects[n].Y())); + } + EXPECT_TRUE(r.Contains(-1, -1)); + EXPECT_TRUE(r.Contains(-1, 200)); + EXPECT_TRUE(r.Contains(200, -1)); + EXPECT_TRUE(r.Contains(200, 200)); + } + for (uint32_t i = 0; i < TestIterations; i++) { + nsRegion r(nsRect(-1, -1, 202, 202)); + nsRegion r2; + for (uint32_t n = 0; n < SubRectsPerTest; n++) { + subRects[n].SetRect(rand() % 100, rand() % 100, rand() % 99 + 1, + rand() % 99 + 1); + r2.OrWith(subRects[n]); + } + r.SubWith(r2); + for (uint32_t n = 0; n < SubRectsPerTest; n++) { + EXPECT_FALSE(r.Contains(subRects[n].x, subRects[n].y)); + EXPECT_FALSE(r.Contains(subRects[n].x, subRects[n].YMost() - 1)); + EXPECT_FALSE( + r.Contains(subRects[n].XMost() - 1, subRects[n].YMost() - 1)); + EXPECT_FALSE(r.Contains(subRects[n].XMost() - 1, subRects[n].Y())); + } + EXPECT_TRUE(r.Contains(-1, -1)); + EXPECT_TRUE(r.Contains(-1, 200)); + EXPECT_TRUE(r.Contains(200, -1)); + EXPECT_TRUE(r.Contains(200, 200)); + } +#endif +} +TEST(Gfx, RegionSub) +{ + { + nsRegion r1(nsRect(0, 0, 100, 50)); + r1.OrWith(nsRect(50, 50, 50, 50)); + nsRegion r2(nsRect(0, 0, 100, 50)); + r2.OrWith(nsRect(50, 50, 50, 50)); + nsRegion r3; + r3.Sub(r1, r2); + EXPECT_FALSE(r3.Contains(1, 1)); + } + { + nsRegion r1(nsRect(0, 0, 800, 1000)); + nsRegion r2(nsRect(8, 108, 22, 20)); + r2.OrWith(nsRect(91, 138, 17, 18)); + nsRegion r3; + r3.Sub(r1, r2); + EXPECT_TRUE(r3.Contains(400, 130)); + } + { + nsRegion r1(nsRect(392, 2, 28, 7)); + r1.OrWith(nsRect(115, 9, 305, 16)); + r1.OrWith(nsRect(392, 25, 28, 5)); + r1.OrWith(nsRect(0, 32, 1280, 41)); + nsRegion r2(nsRect(0, 0, 1280, 9)); + r2.OrWith(nsRect(0, 9, 115, 16)); + r2.OrWith(nsRect(331, 9, 949, 16)); + r2.OrWith(nsRect(0, 25, 1280, 7)); + r2.OrWith(nsRect(331, 32, 124, 1)); + nsRegion r3; + r3.Sub(r1, r2); + EXPECT_FALSE(r3.Contains(350, 15)); + } + { + nsRegion r1(nsRect(552, 0, 2, 2)); + r1.OrWith(nsRect(362, 2, 222, 28)); + r1.OrWith(nsRect(552, 30, 2, 2)); + r1.OrWith(nsRect(0, 32, 1280, 41)); + nsRegion r2(nsRect(512, 0, 146, 9)); + r2.OrWith(nsRect(340, 9, 318, 16)); + r2.OrWith(nsRect(512, 25, 146, 8)); + nsRegion r3; + r3.Sub(r1, r2); + EXPECT_FALSE(r3.Contains(350, 15)); + } + { + nsRegion r1(nsRect(0, 0, 1265, 1024)); + nsRegion r2(nsRect(1265, 0, 15, 685)); + r2.OrWith(nsRect(0, 714, 1280, 221)); + nsRegion r3; + r3.Sub(r1, r2); + } + { + nsRegion r1(nsRect(6, 0, 64, 1)); + r1.OrWith(nsRect(6, 1, 67, 1)); + r1.OrWith(nsRect(6, 2, 67, 2)); + r1.OrWith(nsRect(79, 2, 67, 2)); + r1.OrWith(nsRect(6, 4, 67, 1)); + r1.OrWith(nsRect(79, 4, 98, 1)); + r1.OrWith(nsRect(6, 5, 171, 18)); + r1.OrWith(nsRect(1, 23, 176, 3)); + r1.OrWith(nsRect(1, 26, 178, 5)); + r1.OrWith(nsRect(1, 31, 176, 9)); + r1.OrWith(nsRect(0, 40, 177, 57)); + r1.OrWith(nsRect(0, 97, 176, 33)); + r1.OrWith(nsRect(0, 130, 12, 17)); + r1.OrWith(nsRect(15, 130, 161, 17)); + r1.OrWith(nsRect(0, 147, 12, 5)); + r1.OrWith(nsRect(15, 147, 111, 5)); + r1.OrWith(nsRect(0, 152, 12, 7)); + r1.OrWith(nsRect(17, 152, 109, 7)); + r1.OrWith(nsRect(0, 159, 12, 2)); + r1.OrWith(nsRect(17, 159, 98, 2)); + r1.OrWith(nsRect(17, 161, 98, 9)); + r1.OrWith(nsRect(27, 170, 63, 21)); + nsRegion r2(nsRect(9, 9, 37, 17)); + r2.OrWith(nsRect(92, 9, 26, 17)); + r2.OrWith(nsRect(9, 26, 37, 9)); + r2.OrWith(nsRect(84, 26, 65, 9)); + r2.OrWith(nsRect(9, 35, 37, 2)); + r2.OrWith(nsRect(51, 35, 98, 2)); + r2.OrWith(nsRect(51, 37, 98, 11)); + r2.OrWith(nsRect(51, 48, 78, 4)); + r2.OrWith(nsRect(87, 52, 42, 7)); + r2.OrWith(nsRect(19, 59, 12, 5)); + r2.OrWith(nsRect(87, 59, 42, 5)); + r2.OrWith(nsRect(19, 64, 12, 9)); + r2.OrWith(nsRect(32, 64, 97, 9)); + r2.OrWith(nsRect(19, 73, 12, 2)); + r2.OrWith(nsRect(32, 73, 104, 2)); + r2.OrWith(nsRect(19, 75, 117, 5)); + r2.OrWith(nsRect(18, 80, 118, 5)); + r2.OrWith(nsRect(18, 85, 111, 38)); + r2.OrWith(nsRect(87, 123, 42, 11)); + nsRegion r3; + r3.Sub(r1, r2); + } + { + nsRegion r1(nsRect(27, 0, 39, 1)); + r1.OrWith(nsRect(86, 0, 22, 1)); + r1.OrWith(nsRect(27, 1, 43, 1)); + r1.OrWith(nsRect(86, 1, 22, 1)); + r1.OrWith(nsRect(27, 2, 43, 1)); + r1.OrWith(nsRect(86, 2, 75, 1)); + r1.OrWith(nsRect(12, 3, 58, 1)); + r1.OrWith(nsRect(86, 3, 75, 1)); + r1.OrWith(nsRect(12, 4, 149, 5)); + r1.OrWith(nsRect(0, 9, 161, 9)); + r1.OrWith(nsRect(0, 18, 167, 17)); + r1.OrWith(nsRect(0, 35, 171, 5)); + r1.OrWith(nsRect(0, 40, 189, 28)); + r1.OrWith(nsRect(0, 68, 171, 16)); + r1.OrWith(nsRect(4, 84, 167, 5)); + r1.OrWith(nsRect(4, 89, 177, 9)); + r1.OrWith(nsRect(1, 98, 180, 59)); + r1.OrWith(nsRect(4, 157, 177, 1)); + r1.OrWith(nsRect(4, 158, 139, 15)); + r1.OrWith(nsRect(17, 173, 126, 2)); + r1.OrWith(nsRect(20, 175, 123, 2)); + r1.OrWith(nsRect(20, 177, 118, 6)); + r1.OrWith(nsRect(20, 183, 84, 2)); + nsRegion r2(nsRect(64, 2, 30, 6)); + r2.OrWith(nsRect(26, 11, 41, 17)); + r2.OrWith(nsRect(19, 28, 48, 23)); + r2.OrWith(nsRect(19, 51, 76, 8)); + r2.OrWith(nsRect(4, 59, 91, 31)); + r2.OrWith(nsRect(19, 90, 76, 29)); + r2.OrWith(nsRect(33, 119, 62, 25)); + r2.OrWith(nsRect(33, 144, 4, 21)); + nsRegion r3; + r3.Sub(r1, r2); + } +#ifdef REGION_RANDOM_STRESS_TESTS + const uint32_t TestIterations = 100000; + const uint32_t RectsPerTest = 100; + const uint32_t SubRectsPerTest = 10; + + nsRect rects[RectsPerTest]; + nsRect subRects[SubRectsPerTest]; + + for (uint32_t i = 0; i < TestIterations; i++) { + nsRegion r; + for (uint32_t n = 0; n < RectsPerTest; n++) { + rects[n].SetRect(rand() % 100, rand() % 100, rand() % 99 + 1, + rand() % 99 + 1); + } + r.SetEmpty(); + for (uint32_t n = 0; n < RectsPerTest; n++) { + r.OrWith(rects[n]); + } + for (uint32_t n = 0; n < RectsPerTest; n++) { + EXPECT_TRUE(r.Contains(rects[n])); + } + for (uint32_t n = 0; n < SubRectsPerTest; n++) { + subRects[n].SetRect(rand() % 100, rand() % 100, rand() % 99 + 1, + rand() % 99 + 1); + } + nsRegion r2; + for (uint32_t n = 0; n < SubRectsPerTest; n++) { + r2.OrWith(subRects[n]); + } + nsRegion r3; + r3.Sub(r, r2); + + for (uint32_t n = 0; n < SubRectsPerTest; n++) { + EXPECT_FALSE(r3.Contains(subRects[n].x, subRects[n].y)); + EXPECT_FALSE(r3.Contains(subRects[n].x, subRects[n].YMost() - 1)); + EXPECT_FALSE( + r3.Contains(subRects[n].XMost() - 1, subRects[n].YMost() - 1)); + EXPECT_FALSE(r3.Contains(subRects[n].XMost() - 1, subRects[n].Y())); + } + } + for (uint32_t i = 0; i < TestIterations; i++) { + nsRegion r(nsRect(-1, -1, 202, 202)); + nsRegion r2; + for (uint32_t n = 0; n < SubRectsPerTest; n++) { + subRects[n].SetRect(rand() % 100, rand() % 100, rand() % 99 + 1, + rand() % 99 + 1); + r2.OrWith(subRects[n]); + } + nsRegion r3; + r3.Sub(r, r2); + for (uint32_t n = 0; n < SubRectsPerTest; n++) { + EXPECT_FALSE(r3.Contains(subRects[n].x, subRects[n].y)); + EXPECT_FALSE(r3.Contains(subRects[n].x, subRects[n].YMost() - 1)); + EXPECT_FALSE( + r3.Contains(subRects[n].XMost() - 1, subRects[n].YMost() - 1)); + EXPECT_FALSE(r3.Contains(subRects[n].XMost() - 1, subRects[n].Y())); + } + EXPECT_TRUE(r3.Contains(-1, -1)); + EXPECT_TRUE(r3.Contains(-1, 200)); + EXPECT_TRUE(r3.Contains(200, -1)); + EXPECT_TRUE(r3.Contains(200, 200)); + } +#endif +} + +TEST(Gfx, RegionAndWith) +{ + { + nsRegion r(nsRect(20, 0, 20, 20)); + r.OrWith(nsRect(0, 20, 40, 20)); + r.AndWith(nsRect(0, 0, 5, 5)); + EXPECT_FALSE(r.Contains(1, 1)); + } + { + nsRegion r1(nsRect(512, 1792, 256, 256)); + nsRegion r2(nsRect(17, 1860, 239, 35)); + r2.OrWith(nsRect(17, 1895, 239, 7)); + r2.OrWith(nsRect(768, 1895, 154, 7)); + r2.OrWith(nsRect(17, 1902, 905, 483)); + r1.AndWith(r2); + } +#ifdef REGION_RANDOM_STRESS_TESTS + const uint32_t TestIterations = 100000; + const uint32_t RectsPerTest = 50; + const uint32_t pointsTested = 100; + + { + nsRect rectsSet1[RectsPerTest]; + nsRect rectsSet2[RectsPerTest]; + + for (uint32_t i = 0; i < TestIterations; i++) { + nsRegion r1; + nsRegion r2; + for (uint32_t n = 0; n < RectsPerTest; n++) { + rectsSet1[n].SetRect(rand() % 100, rand() % 100, rand() % 99 + 1, + rand() % 99 + 1); + rectsSet2[n].SetRect(rand() % 100, rand() % 100, rand() % 99 + 1, + rand() % 99 + 1); + r1.OrWith(rectsSet1[n]); + r2.OrWith(rectsSet1[n]); + } + + nsRegion r3 = r1; + r3.AndWith(r2); + + for (uint32_t n = 0; n < pointsTested; n++) { + nsPoint p(rand() % 200, rand() % 200); + if (r1.Contains(p.x, p.y) && r2.Contains(p.x, p.y)) { + EXPECT_TRUE(r3.Contains(p.x, p.y)); + } else { + EXPECT_FALSE(r3.Contains(p.x, p.y)); + } + } + } + } + + { + nsRect rectsSet[RectsPerTest]; + nsRect testRect; + for (uint32_t i = 0; i < TestIterations; i++) { + nsRegion r; + for (uint32_t n = 0; n < RectsPerTest; n++) { + rectsSet[n].SetRect(rand() % 100, rand() % 100, rand() % 99 + 1, + rand() % 99 + 1); + } + for (uint32_t n = 0; n < RectsPerTest; n++) { + r.OrWith(rectsSet[n]); + } + testRect.SetRect(rand() % 100, rand() % 100, rand() % 99 + 1, + rand() % 99 + 1); + + nsRegion r2 = r; + r2.AndWith(testRect); + + for (uint32_t n = 0; n < pointsTested; n++) { + nsPoint p(rand() % 200, rand() % 200); + if (r.Contains(p.x, p.y) && testRect.Contains(p.x, p.y)) { + EXPECT_TRUE(r2.Contains(p.x, p.y)); + } else { + EXPECT_FALSE(r2.Contains(p.x, p.y)); + } + } + } + } +#endif +} + +TEST(Gfx, RegionAnd) +{ + { + nsRegion r(nsRect(20, 0, 20, 20)); + r.OrWith(nsRect(0, 20, 40, 20)); + nsRegion r2; + r2.And(r, nsRect(0, 0, 5, 5)); + EXPECT_FALSE(r.Contains(1, 1)); + } + { + nsRegion r(nsRect(51, 2, 57, 5)); + r.OrWith(nsRect(36, 7, 72, 4)); + r.OrWith(nsRect(36, 11, 25, 1)); + r.OrWith(nsRect(69, 12, 6, 4)); + r.OrWith(nsRect(37, 16, 54, 2)); + r.OrWith(nsRect(37, 18, 82, 2)); + r.OrWith(nsRect(10, 20, 109, 3)); + r.OrWith(nsRect(1, 23, 136, 21)); + r.OrWith(nsRect(1, 44, 148, 2)); + r.OrWith(nsRect(1, 46, 176, 31)); + r.OrWith(nsRect(6, 77, 171, 1)); + r.OrWith(nsRect(5, 78, 172, 30)); + r.OrWith(nsRect(5, 108, 165, 45)); + r.OrWith(nsRect(5, 153, 61, 5)); + r.OrWith(nsRect(72, 153, 98, 5)); + r.OrWith(nsRect(38, 158, 25, 4)); + r.OrWith(nsRect(72, 158, 98, 4)); + r.OrWith(nsRect(58, 162, 5, 8)); + r.OrWith(nsRect(72, 162, 98, 8)); + r.OrWith(nsRect(72, 170, 98, 5)); + nsRegion r2; + r.And(r2, nsRect(18, 78, 53, 45)); + } +#ifdef REGION_RANDOM_STRESS_TESTS + const uint32_t TestIterations = 100000; + const uint32_t RectsPerTest = 50; + const uint32_t pointsTested = 100; + + { + nsRect rectsSet1[RectsPerTest]; + nsRect rectsSet2[RectsPerTest]; + + for (uint32_t i = 0; i < TestIterations; i++) { + nsRegion r1; + nsRegion r2; + for (uint32_t n = 0; n < RectsPerTest; n++) { + rectsSet1[n].SetRect(rand() % 100, rand() % 100, rand() % 99 + 1, + rand() % 99 + 1); + rectsSet2[n].SetRect(rand() % 100, rand() % 100, rand() % 99 + 1, + rand() % 99 + 1); + r1.OrWith(rectsSet1[n]); + r2.OrWith(rectsSet1[n]); + } + + nsRegion r3; + r3.And(r1, r2); + + for (uint32_t n = 0; n < pointsTested; n++) { + nsPoint p(rand() % 200, rand() % 200); + if (r1.Contains(p.x, p.y) && r2.Contains(p.x, p.y)) { + EXPECT_TRUE(r3.Contains(p.x, p.y)); + } else { + EXPECT_FALSE(r3.Contains(p.x, p.y)); + } + } + } + } + + { + nsRect rectsSet[RectsPerTest]; + nsRect testRect; + for (uint32_t i = 0; i < TestIterations; i++) { + nsRegion r; + for (uint32_t n = 0; n < RectsPerTest; n++) { + rectsSet[n].SetRect(rand() % 100, rand() % 100, rand() % 99 + 1, + rand() % 99 + 1); + } + for (uint32_t n = 0; n < RectsPerTest; n++) { + r.OrWith(rectsSet[n]); + } + testRect.SetRect(rand() % 100, rand() % 100, rand() % 99 + 1, + rand() % 99 + 1); + + nsRegion r2; + r2.And(r, testRect); + + for (uint32_t n = 0; n < pointsTested; n++) { + nsPoint p(rand() % 200, rand() % 200); + if (r.Contains(p.x, p.y) && testRect.Contains(p.x, p.y)) { + EXPECT_TRUE(r2.Contains(p.x, p.y)); + } else { + EXPECT_FALSE(r2.Contains(p.x, p.y)); + } + } + } + } +#endif +} + +TEST(Gfx, RegionSimplify) +{ + { // ensure simplify works on a single rect + nsRegion r(nsRect(0, 100, 200, 100)); + + r.SimplifyOutwardByArea(100 * 100); + + nsRegion result(nsRect(0, 100, 200, 100)); + + EXPECT_TRUE(r.IsEqual(result)) << "regions not the same"; + } + + { // the rectangles will be merged + nsRegion r(nsRect(0, 100, 200, 100)); + r.Or(r, nsRect(0, 200, 300, 200)); + + r.SimplifyOutwardByArea(100 * 100); + + nsRegion result(nsRect(0, 100, 300, 300)); + + EXPECT_TRUE(r.IsEqual(result)) << "regions not merged"; + } + + { // two rectangle on the first span + // one on the second + nsRegion r(nsRect(0, 100, 200, 100)); + r.Or(r, nsRect(0, 200, 300, 200)); + r.Or(r, nsRect(250, 100, 50, 100)); + + EXPECT_TRUE(r.GetNumRects() == 3) << "wrong number of rects"; + + r.SimplifyOutwardByArea(100 * 100); + + nsRegion result(nsRect(0, 100, 300, 300)); + + EXPECT_TRUE(r.IsEqual(result)) << "regions not merged"; + } + + { // the rectangles will be merged + nsRegion r(nsRect(0, 100, 200, 100)); + r.Or(r, nsRect(0, 200, 300, 200)); + r.Or(r, nsRect(250, 100, 50, 100)); + r.Sub(r, nsRect(200, 200, 40, 200)); + + EXPECT_TRUE(r.GetNumRects() == 4) << "wrong number of rects"; + + r.SimplifyOutwardByArea(100 * 100); + + nsRegion result(nsRect(0, 100, 300, 300)); + result.Sub(result, nsRect(200, 100, 40, 300)); + + EXPECT_TRUE(r.IsEqual(result)) << "regions not merged"; + } + + { // three spans of rectangles + nsRegion r(nsRect(0, 100, 200, 100)); + r.Or(r, nsRect(0, 200, 300, 200)); + r.Or(r, nsRect(250, 100, 50, 50)); + r.Sub(r, nsRect(200, 200, 40, 200)); + + r.SimplifyOutwardByArea(100 * 100); + + nsRegion result(nsRect(0, 100, 300, 300)); + result.Sub(result, nsRect(200, 100, 40, 300)); + + EXPECT_TRUE(r.IsEqual(result)) << "regions not merged"; + } + + { // three spans of rectangles and an unmerged rectangle + nsRegion r(nsRect(0, 100, 200, 100)); + r.Or(r, nsRect(0, 200, 300, 200)); + r.Or(r, nsRect(250, 100, 50, 50)); + r.Sub(r, nsRect(200, 200, 40, 200)); + r.Or(r, nsRect(250, 900, 150, 50)); + + r.SimplifyOutwardByArea(100 * 100); + + nsRegion result(nsRect(0, 100, 300, 300)); + result.Sub(result, nsRect(200, 100, 40, 300)); + result.Or(result, nsRect(250, 900, 150, 50)); + + EXPECT_TRUE(r.IsEqual(result)) << "regions not merged"; + } + + { // unmerged regions + nsRegion r(nsRect(0, 100, 200, 100)); + r.Or(r, nsRect(0, 200, 300, 200)); + + r.SimplifyOutwardByArea(100); + + nsRegion result(nsRect(0, 100, 200, 100)); + result.Or(result, nsRect(0, 200, 300, 200)); + + EXPECT_TRUE(r.IsEqual(result)) << "regions not merged"; + } + + { // empty region + // just make sure this doesn't crash. + nsRegion r; + r.SimplifyOutwardByArea(100); + } +} + +TEST(Gfx, RegionContains) +{ + { // ensure Contains works on a simple region + nsRegion r(nsRect(0, 0, 100, 100)); + + EXPECT_TRUE(r.Contains(0, 0)); + EXPECT_TRUE(r.Contains(0, 99)); + EXPECT_TRUE(r.Contains(99, 0)); + EXPECT_TRUE(r.Contains(99, 99)); + + EXPECT_FALSE(r.Contains(-1, 50)); + EXPECT_FALSE(r.Contains(100, 50)); + EXPECT_FALSE(r.Contains(50, -1)); + EXPECT_FALSE(r.Contains(50, 100)); + + EXPECT_TRUE(r.Contains(nsRect(0, 0, 100, 100))); + EXPECT_TRUE(r.Contains(nsRect(99, 99, 1, 1))); + + EXPECT_FALSE(r.Contains(nsRect(100, 100, 1, 1))); + EXPECT_FALSE(r.Contains(nsRect(100, 100, 0, 0))); + } + + { // empty regions contain nothing + nsRegion r(nsRect(100, 100, 0, 0)); + + EXPECT_FALSE(r.Contains(0, 0)); + EXPECT_FALSE(r.Contains(100, 100)); + EXPECT_FALSE(r.Contains(nsRect(100, 100, 0, 0))); + EXPECT_FALSE(r.Contains(nsRect(100, 100, 1, 1))); + } + + { // complex region contain tests + // The region looks like this, with two squares that overlap. + // (hard to do accurately with ASCII art) + // +------+ + // | | + // | +--+ + // | | + // +--+ | + // | | + // +------+ + nsRegion r(nsRect(0, 0, 100, 100)); + r.OrWith(nsRect(50, 50, 100, 100)); + + EXPECT_TRUE(r.Contains(0, 0)); + EXPECT_TRUE(r.Contains(99, 99)); + EXPECT_TRUE(r.Contains(50, 100)); + EXPECT_TRUE(r.Contains(100, 50)); + EXPECT_TRUE(r.Contains(149, 149)); + + EXPECT_FALSE(r.Contains(49, 100)); + EXPECT_FALSE(r.Contains(100, 49)); + EXPECT_FALSE(r.Contains(150, 150)); + + EXPECT_TRUE(r.Contains(nsRect(100, 100, 1, 1))); + EXPECT_FALSE(r.Contains(nsRect(49, 99, 2, 2))); + } + + { // region with a hole + nsRegion r(nsRect(0, 0, 100, 100)); + r.SubOut(nsRect(40, 40, 10, 10)); + + EXPECT_TRUE(r.Contains(0, 0)); + EXPECT_TRUE(r.Contains(39, 39)); + EXPECT_FALSE(r.Contains(40, 40)); + EXPECT_FALSE(r.Contains(49, 49)); + EXPECT_TRUE(r.Contains(50, 50)); + + EXPECT_FALSE(r.Contains(nsRect(40, 40, 10, 10))); + EXPECT_FALSE(r.Contains(nsRect(39, 39, 2, 2))); + } +} + +#define DILATE_VALUE 0x88 +#define REGION_VALUE 0xff + +struct RegionBitmap { + RegionBitmap(unsigned char* bitmap, int width, int height) + : bitmap(bitmap), width(width), height(height) {} + + void clear() { + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + bitmap[x + y * width] = 0; + } + } + } + + void set(nsRegion& region) { + clear(); + for (auto iter = region.RectIter(); !iter.Done(); iter.Next()) { + const nsRect& r = iter.Get(); + for (int y = r.Y(); y < r.YMost(); y++) { + for (int x = r.X(); x < r.XMost(); x++) { + bitmap[x + y * width] = REGION_VALUE; + } + } + } + } + + void dilate() { + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + if (bitmap[x + y * width] == REGION_VALUE) { + for (int yn = std::max(y - 1, 0); yn <= std::min(y + 1, height - 1); + yn++) { + for (int xn = std::max(x - 1, 0); xn <= std::min(x + 1, width - 1); + xn++) { + if (bitmap[xn + yn * width] == 0) + bitmap[xn + yn * width] = DILATE_VALUE; + } + } + } + } + } + } + void compare(RegionBitmap& reference) { + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + EXPECT_EQ(bitmap[x + y * width], reference.bitmap[x + y * width]); + } + } + } + + unsigned char* bitmap; + int width; + int height; +}; + +static void VisitEdge(void* closure, VisitSide side, int x1, int y1, int x2, + int y2) { + EXPECT_GE(x2, x1); + RegionBitmap* visitor = static_cast<RegionBitmap*>(closure); + unsigned char* bitmap = visitor->bitmap; + const int width = visitor->width; + + if (side == VisitSide::TOP) { + while (x1 != x2) { + bitmap[x1 + (y1 - 1) * width] = DILATE_VALUE; + x1++; + } + } else if (side == VisitSide::BOTTOM) { + while (x1 != x2) { + bitmap[x1 + y1 * width] = DILATE_VALUE; + x1++; + } + } else if (side == VisitSide::LEFT) { + while (y1 != y2) { + bitmap[x1 - 1 + y1 * width] = DILATE_VALUE; + y1++; + } + } else if (side == VisitSide::RIGHT) { + while (y1 != y2) { + bitmap[x1 + y1 * width] = DILATE_VALUE; + y1++; + } + } +} + +static void TestVisit(nsRegion& r) { + auto reference = mozilla::MakeUnique<unsigned char[]>(600 * 600); + auto result = mozilla::MakeUnique<unsigned char[]>(600 * 600); + RegionBitmap ref(reference.get(), 600, 600); + RegionBitmap res(result.get(), 600, 600); + + ref.set(r); + ref.dilate(); + + res.set(r); + r.VisitEdges(VisitEdge, &res); + res.compare(ref); +} + +TEST(Gfx, RegionVisitEdges) +{ + { // visit edges + nsRegion r(nsRect(20, 20, 100, 100)); + r.Or(r, nsRect(20, 120, 200, 100)); + TestVisit(r); + } + + { // two rects side by side - 1 pixel inbetween + nsRegion r(nsRect(20, 20, 100, 100)); + r.Or(r, nsRect(121, 20, 100, 100)); + TestVisit(r); + } + + { // two rects side by side - 2 pixels inbetween + nsRegion r(nsRect(20, 20, 100, 100)); + r.Or(r, nsRect(122, 20, 100, 100)); + TestVisit(r); + } + + { + // only corner of the rects are touching + nsRegion r(nsRect(20, 20, 100, 100)); + r.Or(r, nsRect(120, 120, 100, 100)); + + TestVisit(r); + } + + { + // corners are 1 pixel away + nsRegion r(nsRect(20, 20, 100, 100)); + r.Or(r, nsRect(121, 120, 100, 100)); + + TestVisit(r); + } + + { + // vertically separated + nsRegion r(nsRect(20, 20, 100, 100)); + r.Or(r, nsRect(120, 125, 100, 100)); + + TestVisit(r); + } + + { + // not touching + nsRegion r(nsRect(20, 20, 100, 100)); + r.Or(r, nsRect(130, 120, 100, 100)); + r.Or(r, nsRect(240, 20, 100, 100)); + + TestVisit(r); + } + + { // rect with a hole in it + nsRegion r(nsRect(20, 20, 100, 100)); + r.Sub(r, nsRect(40, 40, 10, 10)); + + TestVisit(r); + } + { + // left overs + nsRegion r(nsRect(20, 20, 10, 10)); + r.Or(r, nsRect(50, 20, 10, 10)); + r.Or(r, nsRect(90, 20, 10, 10)); + r.Or(r, nsRect(24, 30, 10, 10)); + r.Or(r, nsRect(20, 40, 15, 10)); + r.Or(r, nsRect(50, 40, 15, 10)); + r.Or(r, nsRect(90, 40, 15, 10)); + + TestVisit(r); + } + + { + // vertically separated + nsRegion r(nsRect(20, 20, 100, 100)); + r.Or(r, nsRect(120, 125, 100, 100)); + + TestVisit(r); + } + + { + // two upper rects followed by a lower one + // on the same line + nsRegion r(nsRect(5, 5, 50, 50)); + r.Or(r, nsRect(100, 5, 50, 50)); + r.Or(r, nsRect(200, 50, 50, 50)); + + TestVisit(r); + } + + { + // bug 1130978. + nsRegion r(nsRect(4, 1, 61, 49)); + r.Or(r, nsRect(115, 1, 99, 49)); + r.Or(r, nsRect(115, 49, 99, 1)); + r.Or(r, nsRect(12, 50, 11, 5)); + r.Or(r, nsRect(25, 50, 28, 5)); + r.Or(r, nsRect(115, 50, 99, 5)); + r.Or(r, nsRect(115, 55, 99, 12)); + + TestVisit(r); + } +} diff --git a/gfx/tests/gtest/TestSkipChars.cpp b/gfx/tests/gtest/TestSkipChars.cpp new file mode 100644 index 0000000000..8f31777619 --- /dev/null +++ b/gfx/tests/gtest/TestSkipChars.cpp @@ -0,0 +1,156 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "gtest/gtest.h" + +#include "gfxSkipChars.h" +#include "mozilla/ArrayUtils.h" + +static bool TestConstructor() { + gfxSkipChars skipChars; + + EXPECT_TRUE(skipChars.GetOriginalCharCount() == 0) + << "[1] Make sure the gfxSkipChars was properly initialized with " + "constructor"; + + return true; +} + +static bool TestLength() { + gfxSkipChars skipChars; + + skipChars.KeepChars(100); + + EXPECT_TRUE(skipChars.GetOriginalCharCount() == 100) + << "[1] Check length after keeping chars"; + + skipChars.SkipChars(50); + + EXPECT_TRUE(skipChars.GetOriginalCharCount() == 150) + << "[2] Check length after skipping chars"; + + skipChars.SkipChars(50); + + EXPECT_TRUE(skipChars.GetOriginalCharCount() == 200) + << "[3] Check length after skipping more chars"; + + skipChars.KeepChar(); + + EXPECT_TRUE(skipChars.GetOriginalCharCount() == 201) + << "[4] Check length after keeping a final char"; + + return true; +} + +static bool TestIterator() { + // Test a gfxSkipChars that starts with kept chars + gfxSkipChars skipChars1; + + skipChars1.KeepChars(9); + skipChars1.SkipChar(); + skipChars1.KeepChars(9); + skipChars1.SkipChar(); + skipChars1.KeepChars(9); + + EXPECT_TRUE(skipChars1.GetOriginalCharCount() == 29) << "[1] Check length"; + + gfxSkipCharsIterator iter1(skipChars1); + + EXPECT_TRUE(iter1.GetOriginalOffset() == 0) + << "[2] Check initial original offset"; + EXPECT_TRUE(iter1.GetSkippedOffset() == 0) + << "[3] Check initial skipped offset"; + + EXPECT_TRUE(iter1.IsOriginalCharSkipped() == false) + << "[3a] Check IsOriginalCharSkipped for initial position"; + + uint32_t expectSkipped1[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, + 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, + 18, 19, 20, 21, 22, 23, 24, 25, 26, 27}; + + for (uint32_t i = 0; i < mozilla::ArrayLength(expectSkipped1); i++) { + EXPECT_TRUE(iter1.ConvertOriginalToSkipped(i) == expectSkipped1[i]) + << "[4] Check mapping of original to skipped for " << i; + } + + int32_t expectOriginal1[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, + 10, 11, 12, 13, 14, 15, 16, 17, 18, + 20, 21, 22, 23, 24, 25, 26, 27, 28}; + + for (uint32_t i = 0; i < mozilla::ArrayLength(expectOriginal1); i++) { + EXPECT_TRUE(iter1.ConvertSkippedToOriginal(i) == expectOriginal1[i]) + << "[5] Check mapping of skipped to original for " << i; + } + + bool expectIsOriginalSkipped1[] = { + false, false, false, false, false, false, false, false, false, true, + false, false, false, false, false, false, false, false, false, true, + false, false, false, false, false, false, false, false, false}; + + for (uint32_t i = 0; i < mozilla::ArrayLength(expectIsOriginalSkipped1); + i++) { + iter1.SetOriginalOffset(i); + EXPECT_TRUE(iter1.IsOriginalCharSkipped() == expectIsOriginalSkipped1[i]) + << "[5.a] Check IsOriginalCharSkipped for " << i; + } + + // Test a gfxSkipChars that starts with skipped chars + gfxSkipChars skipChars2; + + skipChars2.SkipChars(9); + skipChars2.KeepChar(); + skipChars2.SkipChars(9); + skipChars2.KeepChar(); + skipChars2.SkipChars(9); + + EXPECT_TRUE(skipChars2.GetOriginalCharCount() == 29) << "[6] Check length"; + + gfxSkipCharsIterator iter2(skipChars2); + + EXPECT_TRUE(iter2.GetOriginalOffset() == 0) + << "[7] Check initial original offset"; + EXPECT_TRUE(iter2.GetSkippedOffset() == 0) + << "[8] Check initial skipped offset"; + + EXPECT_TRUE(iter2.IsOriginalCharSkipped() == true) + << "[8a] Check IsOriginalCharSkipped for initial position"; + + uint32_t expectSkipped2[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2}; + + for (uint32_t i = 0; i < mozilla::ArrayLength(expectSkipped2); i++) { + EXPECT_TRUE(iter2.ConvertOriginalToSkipped(i) == expectSkipped2[i]) + << "[9] Check mapping of original to skipped for " << i; + } + + int32_t expectOriginal2[] = {9, 19, 29}; + + for (uint32_t i = 0; i < mozilla::ArrayLength(expectOriginal2); i++) { + EXPECT_TRUE(iter2.ConvertSkippedToOriginal(i) == expectOriginal2[i]) + << "[10] Check mapping of skipped to original for " << i; + } + + bool expectIsOriginalSkipped2[] = { + true, true, true, true, true, true, true, true, true, false, + true, true, true, true, true, true, true, true, true, false, + true, true, true, true, true, true, true, true, true}; + + for (uint32_t i = 0; i < mozilla::ArrayLength(expectIsOriginalSkipped2); + i++) { + iter2.SetOriginalOffset(i); + EXPECT_TRUE(iter2.IsOriginalCharSkipped() == expectIsOriginalSkipped2[i]) + << "[10.a] Check IsOriginalCharSkipped for " << i; + } + + return true; +} + +TEST(Gfx, gfxSkipChars) +{ + TestConstructor(); + TestLength(); + TestIterator(); +} diff --git a/gfx/tests/gtest/TestSwizzle.cpp b/gfx/tests/gtest/TestSwizzle.cpp new file mode 100644 index 0000000000..c63b3cd682 --- /dev/null +++ b/gfx/tests/gtest/TestSwizzle.cpp @@ -0,0 +1,696 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "gtest/gtest.h" +#include "mozilla/ArrayUtils.h" +#include "mozilla/gfx/Swizzle.h" +#include "Orientation.h" + +using namespace mozilla; +using namespace mozilla::gfx; +using namespace mozilla::image; + +TEST(Moz2D, PremultiplyData) +{ + const uint8_t in_bgra[5 * 4] = { + 255, 255, 0, 255, // verify 255 alpha leaves RGB unchanged + 0, 0, 255, 255, + 0, 255, 255, 0, // verify 0 alpha zeroes out RGB + 0, 0, 0, 0, + 255, 0, 0, 128, // verify that 255 RGB maps to alpha + }; + uint8_t out[5 * 4]; + const uint8_t check_bgra[5 * 4] = { + 255, 255, 0, 255, 0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 128, 0, 0, 128, + }; + // check swizzled output + const uint8_t check_rgba[5 * 4] = { + 0, 255, 255, 255, 255, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 128, + }; + const uint8_t check_argb[5 * 4] = { + 255, 0, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 0, 0, 128, + }; + + PremultiplyData(in_bgra, sizeof(in_bgra), SurfaceFormat::B8G8R8A8, out, + sizeof(in_bgra), SurfaceFormat::B8G8R8A8, IntSize(5, 1)); + EXPECT_TRUE(ArrayEqual(out, check_bgra)); + + PremultiplyData(in_bgra, sizeof(in_bgra), SurfaceFormat::B8G8R8A8, out, + sizeof(in_bgra), SurfaceFormat::R8G8B8A8, IntSize(5, 1)); + EXPECT_TRUE(ArrayEqual(out, check_rgba)); + + PremultiplyData(in_bgra, sizeof(in_bgra), SurfaceFormat::B8G8R8A8, out, + sizeof(in_bgra), SurfaceFormat::A8R8G8B8, IntSize(5, 1)); + EXPECT_TRUE(ArrayEqual(out, check_argb)); +} + +TEST(Moz2D, PremultiplyRow) +{ + const uint8_t in_bgra[5 * 4] = { + 255, 255, 0, 255, // verify 255 alpha leaves RGB unchanged + 0, 0, 255, 255, + 0, 255, 255, 0, // verify 0 alpha zeroes out RGB + 0, 0, 0, 0, + 255, 0, 0, 128, // verify that 255 RGB maps to alpha + }; + uint8_t out[5 * 4]; + const uint8_t check_bgra[5 * 4] = { + 255, 255, 0, 255, 0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 128, 0, 0, 128, + }; + // check swizzled output + const uint8_t check_rgba[5 * 4] = { + 0, 255, 255, 255, 255, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 128, + }; + const uint8_t check_argb[5 * 4] = { + 255, 0, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 0, 0, 128, + }; + + SwizzleRowFn func = + PremultiplyRow(SurfaceFormat::B8G8R8A8, SurfaceFormat::B8G8R8A8); + func(in_bgra, out, 5); + EXPECT_TRUE(ArrayEqual(out, check_bgra)); + + func = PremultiplyRow(SurfaceFormat::B8G8R8A8, SurfaceFormat::R8G8B8A8); + func(in_bgra, out, 5); + EXPECT_TRUE(ArrayEqual(out, check_rgba)); + + func = PremultiplyRow(SurfaceFormat::B8G8R8A8, SurfaceFormat::A8R8G8B8); + func(in_bgra, out, 5); + EXPECT_TRUE(ArrayEqual(out, check_argb)); +} + +TEST(Moz2D, PremultiplyYFlipData) +{ + const uint8_t stride = 2 * 4; + const uint8_t in_bgra[6 * 4] = { + 255, 255, 0, 255, // row 1: verify 255 alpha leaves RGB unchanged + 0, 0, 255, 255, + 0, 255, 255, 0, // row 2: verify 0 alpha zeroes out RGB + 0, 0, 0, 0, + 255, 0, 0, 128, // row 3: verify that 255 RGB maps to alpha + 255, 255, 255, 128, + }; + const uint8_t in_bgra_2[4 * 4] = { + 255, 255, 0, 255, // row 1: verify 255 alpha leaves RGB unchanged + 0, 0, 255, 255, + 0, 255, 255, 0, // row 2: verify 0 alpha zeroes out RGB + 0, 0, 0, 0, + }; + const uint8_t in_bgra_3[2 * 4] = { + 255, 0, 0, 128, // row 1: verify that 255 RGB maps to alpha + 255, 255, 255, 128, + }; + uint8_t out[6 * 4]; + uint8_t out_2[4 * 4]; + uint8_t out_3[2 * 4]; + const uint8_t check_bgra[6 * 4] = { + 128, 0, 0, 128, 128, 128, 128, 128, 0, 0, 0, 0, + 0, 0, 0, 0, 255, 255, 0, 255, 0, 0, 255, 255, + }; + const uint8_t check_bgra_2[4 * 4] = { + 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 0, 255, 0, 0, 255, 255, + }; + const uint8_t check_bgra_3[2 * 4] = { + 128, 0, 0, 128, 128, 128, 128, 128, + }; + // check swizzled output + const uint8_t check_rgba[6 * 4] = { + 0, 0, 128, 128, 128, 128, 128, 128, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 255, + }; + + // Premultiply. + PremultiplyYFlipData(in_bgra, stride, SurfaceFormat::B8G8R8A8, out, stride, + SurfaceFormat::B8G8R8A8, IntSize(2, 3)); + EXPECT_TRUE(ArrayEqual(out, check_bgra)); + + // Premultiply in-place with middle row. + memcpy(out, in_bgra, sizeof(out)); + PremultiplyYFlipData(out, stride, SurfaceFormat::B8G8R8A8, out, stride, + SurfaceFormat::B8G8R8A8, IntSize(2, 3)); + EXPECT_TRUE(ArrayEqual(out, check_bgra)); + + // Premultiply in-place without middle row. + memcpy(out_2, in_bgra_2, sizeof(out_2)); + PremultiplyYFlipData(out_2, stride, SurfaceFormat::B8G8R8A8, out_2, stride, + SurfaceFormat::B8G8R8A8, IntSize(2, 2)); + EXPECT_TRUE(ArrayEqual(out_2, check_bgra_2)); + + // Premultiply in-place only middle row. + memcpy(out_3, in_bgra_3, sizeof(out_3)); + PremultiplyYFlipData(out_3, stride, SurfaceFormat::B8G8R8A8, out_3, stride, + SurfaceFormat::B8G8R8A8, IntSize(2, 1)); + EXPECT_TRUE(ArrayEqual(out_3, check_bgra_3)); + + // Premultiply and swizzle with middle row. + PremultiplyYFlipData(in_bgra, stride, SurfaceFormat::B8G8R8A8, out, stride, + SurfaceFormat::R8G8B8A8, IntSize(2, 3)); + EXPECT_TRUE(ArrayEqual(out, check_rgba)); +} + +TEST(Moz2D, UnpremultiplyData) +{ + const uint8_t in_bgra[5 * 4] = { + 255, 255, 0, 255, // verify 255 alpha leaves RGB unchanged + 0, 0, 255, 255, 0, 0, 0, 0, // verify 0 alpha leaves RGB at 0 + 0, 0, 0, 64, // verify 0 RGB stays 0 with non-zero alpha + 128, 0, 0, 128, // verify that RGB == alpha maps to 255 + + }; + uint8_t out[5 * 4]; + const uint8_t check_bgra[5 * 4] = { + 255, 255, 0, 255, 0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 0, 64, 255, 0, 0, 128, + }; + // check swizzled output + const uint8_t check_rgba[5 * 4] = { + 0, 255, 255, 255, 255, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 64, 0, 0, 255, 128, + }; + const uint8_t check_argb[5 * 4] = { + 255, 0, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 64, 0, 0, 0, 128, 0, 0, 255, + }; + + UnpremultiplyData(in_bgra, sizeof(in_bgra), SurfaceFormat::B8G8R8A8, out, + sizeof(in_bgra), SurfaceFormat::B8G8R8A8, IntSize(5, 1)); + EXPECT_TRUE(ArrayEqual(out, check_bgra)); + + UnpremultiplyData(in_bgra, sizeof(in_bgra), SurfaceFormat::B8G8R8A8, out, + sizeof(in_bgra), SurfaceFormat::R8G8B8A8, IntSize(5, 1)); + EXPECT_TRUE(ArrayEqual(out, check_rgba)); + + UnpremultiplyData(in_bgra, sizeof(in_bgra), SurfaceFormat::B8G8R8A8, out, + sizeof(in_bgra), SurfaceFormat::A8R8G8B8, IntSize(5, 1)); + EXPECT_TRUE(ArrayEqual(out, check_argb)); +} + +TEST(Moz2D, UnpremultiplyRow) +{ + const uint8_t in_bgra[5 * 4] = { + 255, 255, 0, 255, // verify 255 alpha leaves RGB unchanged + 0, 0, 255, 255, 0, 0, 0, 0, // verify 0 alpha leaves RGB at 0 + 0, 0, 0, 64, // verify 0 RGB stays 0 with non-zero alpha + 128, 0, 0, 128, // verify that RGB == alpha maps to 255 + + }; + uint8_t out[5 * 4]; + const uint8_t check_bgra[5 * 4] = { + 255, 255, 0, 255, 0, 0, 255, 255, 0, 0, 0, 0, 0, 0, 0, 64, 255, 0, 0, 128, + }; + // check swizzled output + const uint8_t check_rgba[5 * 4] = { + 0, 255, 255, 255, 255, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 64, 0, 0, 255, 128, + }; + const uint8_t check_argb[5 * 4] = { + 255, 0, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 64, 0, 0, 0, 128, 0, 0, 255, + }; + + SwizzleRowFn func = + UnpremultiplyRow(SurfaceFormat::B8G8R8A8, SurfaceFormat::B8G8R8A8); + func(in_bgra, out, 5); + EXPECT_TRUE(ArrayEqual(out, check_bgra)); + + func = UnpremultiplyRow(SurfaceFormat::B8G8R8A8, SurfaceFormat::R8G8B8A8); + func(in_bgra, out, 5); + EXPECT_TRUE(ArrayEqual(out, check_rgba)); + + func = UnpremultiplyRow(SurfaceFormat::B8G8R8A8, SurfaceFormat::A8R8G8B8); + func(in_bgra, out, 5); + EXPECT_TRUE(ArrayEqual(out, check_argb)); +} + +TEST(Moz2D, SwizzleData) +{ + const uint8_t in_bgra[5 * 4] = { + 253, 254, 0, 255, 0, 0, 255, 255, 0, 0, 0, 0, 1, 2, 3, 64, 127, 0, 9, 128, + + }; + uint8_t out[5 * 4]; + // check copy + const uint8_t check_bgra[5 * 4] = { + 253, 254, 0, 255, 0, 0, 255, 255, 0, 0, 0, 0, 1, 2, 3, 64, 127, 0, 9, 128, + }; + // check swaps + const uint8_t check_rgba[5 * 4] = { + 0, 254, 253, 255, 255, 0, 0, 255, 0, 0, 0, 0, 3, 2, 1, 64, 9, 0, 127, 128, + }; + const uint8_t check_argb[5 * 4] = { + 255, 0, 254, 253, 255, 255, 0, 0, 0, 0, 0, 0, 64, 3, 2, 1, 128, 9, 0, 127, + }; + // check opaquifying + const uint8_t check_rgbx[5 * 4] = { + 0, 254, 253, 255, 255, 0, 0, 255, 0, 0, + 0, 255, 3, 2, 1, 255, 9, 0, 127, 255, + }; + // check packing + uint8_t out24[5 * 3]; + const uint8_t check_bgr[5 * 3] = {253, 254, 0, 0, 0, 255, 0, 0, + 0, 1, 2, 3, 127, 0, 9}; + const uint8_t check_rgb[5 * 3] = { + 0, 254, 253, 255, 0, 0, 0, 0, 0, 3, 2, 1, 9, 0, 127, + }; + uint8_t out8[5]; + const uint8_t check_a[5] = {255, 255, 0, 64, 128}; + uint16_t out16[5]; +#define PACK_RGB565(b, g, r) \ + (((b & 0xF8) >> 3) | ((g & 0xFC) << 3) | ((r & 0xF8) << 8)) + const uint16_t check_16[5] = { + PACK_RGB565(253, 254, 0), PACK_RGB565(0, 0, 255), PACK_RGB565(0, 0, 0), + PACK_RGB565(1, 2, 3), PACK_RGB565(127, 0, 9), + }; + + SwizzleData(in_bgra, sizeof(in_bgra), SurfaceFormat::B8G8R8A8, out, + sizeof(out), SurfaceFormat::B8G8R8A8, IntSize(5, 1)); + EXPECT_TRUE(ArrayEqual(out, check_bgra)); + + SwizzleData(in_bgra, sizeof(in_bgra), SurfaceFormat::B8G8R8A8, out, + sizeof(out), SurfaceFormat::R8G8B8A8, IntSize(5, 1)); + EXPECT_TRUE(ArrayEqual(out, check_rgba)); + + SwizzleData(in_bgra, sizeof(in_bgra), SurfaceFormat::B8G8R8A8, out, + sizeof(out), SurfaceFormat::A8R8G8B8, IntSize(5, 1)); + EXPECT_TRUE(ArrayEqual(out, check_argb)); + + SwizzleData(in_bgra, sizeof(in_bgra), SurfaceFormat::B8G8R8A8, out, + sizeof(out), SurfaceFormat::R8G8B8X8, IntSize(5, 1)); + EXPECT_TRUE(ArrayEqual(out, check_rgbx)); + + SwizzleData(in_bgra, sizeof(in_bgra), SurfaceFormat::B8G8R8A8, out24, + sizeof(out24), SurfaceFormat::B8G8R8, IntSize(5, 1)); + EXPECT_TRUE(ArrayEqual(out24, check_bgr)); + + SwizzleData(in_bgra, sizeof(in_bgra), SurfaceFormat::B8G8R8A8, out24, + sizeof(out24), SurfaceFormat::R8G8B8, IntSize(5, 1)); + EXPECT_TRUE(ArrayEqual(out24, check_rgb)); + + SwizzleData(in_bgra, sizeof(in_bgra), SurfaceFormat::B8G8R8A8, out8, + sizeof(out8), SurfaceFormat::A8, IntSize(5, 1)); + EXPECT_TRUE(ArrayEqual(out8, check_a)); + + const uint8_t* uint32_argb; + +#if MOZ_BIG_ENDIAN() + EXPECT_EQ(SurfaceFormat::A8R8G8B8_UINT32, SurfaceFormat::A8R8G8B8); + uint32_argb = check_argb; +#else + EXPECT_EQ(SurfaceFormat::A8R8G8B8_UINT32, SurfaceFormat::B8G8R8A8); + uint32_argb = check_bgra; +#endif + + SwizzleData(uint32_argb, sizeof(in_bgra), SurfaceFormat::A8R8G8B8_UINT32, + reinterpret_cast<uint8_t*>(out16), sizeof(out16), + SurfaceFormat::R5G6B5_UINT16, IntSize(5, 1)); + EXPECT_TRUE(ArrayEqual(out16, check_16)); +} + +TEST(Moz2D, SwizzleYFlipData) +{ + const uint8_t stride = 2 * 4; + const uint8_t in_bgra[6 * 4] = { + 255, 255, 0, 255, // row 1 + 0, 0, 255, 255, 0, 255, 255, 0, // row 2 + 0, 0, 0, 0, 255, 0, 0, 128, // row 3 + 255, 255, 255, 128, + }; + const uint8_t in_bgra_2[4 * 4] = { + 255, 255, 0, 255, // row 1 + 0, 0, 255, 255, 0, 255, 255, 0, // row 2 + 0, 0, 0, 0, + }; + const uint8_t in_bgra_3[2 * 4] = { + 255, 0, 0, 128, // row 1 + 255, 255, 255, 128, + }; + uint8_t out[6 * 4]; + uint8_t out_2[4 * 4]; + uint8_t out_3[2 * 4]; + const uint8_t check_rgba[6 * 4] = { + 0, 0, 255, 128, 255, 255, 255, 128, 255, 255, 0, 0, + 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 255, + }; + const uint8_t check_rgba_2[4 * 4] = { + 255, 255, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 255, + }; + const uint8_t check_rgba_3[2 * 4] = { + 0, 0, 255, 128, 255, 255, 255, 128, + }; + + // Swizzle. + SwizzleYFlipData(in_bgra, stride, SurfaceFormat::B8G8R8A8, out, stride, + SurfaceFormat::R8G8B8A8, IntSize(2, 3)); + EXPECT_TRUE(ArrayEqual(out, check_rgba)); + + // Swizzle in-place with middle row. + memcpy(out, in_bgra, sizeof(out)); + SwizzleYFlipData(out, stride, SurfaceFormat::B8G8R8A8, out, stride, + SurfaceFormat::R8G8B8A8, IntSize(2, 3)); + EXPECT_TRUE(ArrayEqual(out, check_rgba)); + + // Swizzle in-place without middle row. + memcpy(out_2, in_bgra_2, sizeof(out_2)); + SwizzleYFlipData(out_2, stride, SurfaceFormat::B8G8R8A8, out_2, stride, + SurfaceFormat::R8G8B8A8, IntSize(2, 2)); + EXPECT_TRUE(ArrayEqual(out_2, check_rgba_2)); + + // Swizzle in-place only middle row. + memcpy(out_3, in_bgra_3, sizeof(out_3)); + SwizzleYFlipData(out_3, stride, SurfaceFormat::B8G8R8A8, out_3, stride, + SurfaceFormat::R8G8B8A8, IntSize(2, 1)); + EXPECT_TRUE(ArrayEqual(out_3, check_rgba_3)); +} + +TEST(Moz2D, SwizzleRow) +{ + const uint8_t in_bgra[5 * 4] = { + 253, 254, 0, 255, 0, 0, 255, 255, 0, 0, 0, 0, 1, 2, 3, 64, 127, 0, 9, 128, + + }; + uint8_t out[5 * 4]; + // check swaps + const uint8_t check_rgba[5 * 4] = { + 0, 254, 253, 255, 255, 0, 0, 255, 0, 0, 0, 0, 3, 2, 1, 64, 9, 0, 127, 128, + }; + // check opaquifying + const uint8_t check_rgbx[5 * 4] = { + 0, 254, 253, 255, 255, 0, 0, 255, 0, 0, + 0, 255, 3, 2, 1, 255, 9, 0, 127, 255, + }; + // check packing + uint8_t out24[5 * 3]; + const uint8_t check_bgr[5 * 3] = {253, 254, 0, 0, 0, 255, 0, 0, + 0, 1, 2, 3, 127, 0, 9}; + const uint8_t check_rgb[5 * 3] = { + 0, 254, 253, 255, 0, 0, 0, 0, 0, 3, 2, 1, 9, 0, 127, + }; + // check unpacking + uint8_t out_unpack[16 * 4]; + const uint8_t in_rgb[16 * 3] = { + 0, 254, 253, 255, 0, 0, 0, 0, 0, 3, 2, 1, 9, 0, 127, 4, + 5, 6, 9, 8, 7, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, + 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, + }; + const uint8_t check_unpack_rgbx[16 * 4] = { + 0, 254, 253, 255, 255, 0, 0, 255, 0, 0, 0, 255, 3, 2, 1, 255, + 9, 0, 127, 255, 4, 5, 6, 255, 9, 8, 7, 255, 10, 11, 12, 255, + 13, 14, 15, 255, 16, 17, 18, 255, 19, 20, 21, 255, 22, 23, 24, 255, + 25, 26, 27, 255, 28, 29, 30, 255, 31, 32, 33, 255, 34, 35, 36, 255, + }; + const uint8_t check_unpack_bgrx[16 * 4] = { + 253, 254, 0, 255, 0, 0, 255, 255, 0, 0, 0, 255, 1, 2, 3, 255, + 127, 0, 9, 255, 6, 5, 4, 255, 7, 8, 9, 255, 12, 11, 10, 255, + 15, 14, 13, 255, 18, 17, 16, 255, 21, 20, 19, 255, 24, 23, 22, 255, + 27, 26, 25, 255, 30, 29, 28, 255, 33, 32, 31, 255, 36, 35, 34, 255, + }; + const uint8_t check_unpack_xrgb[16 * 4] = { + 255, 0, 254, 253, 255, 255, 0, 0, 255, 0, 0, 0, 255, 3, 2, 1, + 255, 9, 0, 127, 255, 4, 5, 6, 255, 9, 8, 7, 255, 10, 11, 12, + 255, 13, 14, 15, 255, 16, 17, 18, 255, 19, 20, 21, 255, 22, 23, 24, + 255, 25, 26, 27, 255, 28, 29, 30, 255, 31, 32, 33, 255, 34, 35, 36, + }; + + SwizzleRowFn func = + SwizzleRow(SurfaceFormat::B8G8R8A8, SurfaceFormat::R8G8B8A8); + func(in_bgra, out, 5); + EXPECT_TRUE(ArrayEqual(out, check_rgba)); + + func = SwizzleRow(SurfaceFormat::B8G8R8A8, SurfaceFormat::R8G8B8X8); + func(in_bgra, out, 5); + EXPECT_TRUE(ArrayEqual(out, check_rgbx)); + + func = SwizzleRow(SurfaceFormat::B8G8R8A8, SurfaceFormat::B8G8R8A8); + func(in_bgra, out, 5); + EXPECT_TRUE(ArrayEqual(out, in_bgra)); + + func = SwizzleRow(SurfaceFormat::B8G8R8A8, SurfaceFormat::B8G8R8); + func(in_bgra, out24, 5); + EXPECT_TRUE(ArrayEqual(out24, check_bgr)); + + func = SwizzleRow(SurfaceFormat::B8G8R8A8, SurfaceFormat::R8G8B8); + func(in_bgra, out24, 5); + EXPECT_TRUE(ArrayEqual(out24, check_rgb)); + + func = SwizzleRow(SurfaceFormat::R8G8B8, SurfaceFormat::B8G8R8X8); + func(in_rgb, out_unpack, 16); + EXPECT_TRUE(ArrayEqual(out_unpack, check_unpack_bgrx)); + + memset(out_unpack, 0xE5, sizeof(out_unpack)); + memcpy(out_unpack, in_rgb, sizeof(in_rgb)); + func(out_unpack, out_unpack, 16); + EXPECT_TRUE(ArrayEqual(out_unpack, check_unpack_bgrx)); + + func = SwizzleRow(SurfaceFormat::R8G8B8, SurfaceFormat::R8G8B8X8); + func(in_rgb, out_unpack, 16); + EXPECT_TRUE(ArrayEqual(out_unpack, check_unpack_rgbx)); + + memset(out_unpack, 0xE5, sizeof(out_unpack)); + memcpy(out_unpack, in_rgb, sizeof(in_rgb)); + func(out_unpack, out_unpack, 16); + EXPECT_TRUE(ArrayEqual(out_unpack, check_unpack_rgbx)); + + func = SwizzleRow(SurfaceFormat::R8G8B8, SurfaceFormat::X8R8G8B8); + func(in_rgb, out_unpack, 16); + EXPECT_TRUE(ArrayEqual(out_unpack, check_unpack_xrgb)); + + memset(out_unpack, 0xE5, sizeof(out_unpack)); + memcpy(out_unpack, in_rgb, sizeof(in_rgb)); + func(out_unpack, out_unpack, 16); + EXPECT_TRUE(ArrayEqual(out_unpack, check_unpack_xrgb)); +} + +TEST(Moz2D, ReorientRow) +{ + // Input is a 3x4 image. + const uint8_t in_row0[3 * 4] = { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, + }; + const uint8_t in_row1[3 * 4] = { + 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, + }; + const uint8_t in_row2[3 * 4] = { + 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, + }; + const uint8_t in_row3[3 * 4] = { + 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, + }; + + // Output is either a 3x4 image or 4x3 image. + uint8_t out[3 * 4 * 4]; + IntSize outSize(3, 4); + IntSize outSizeSwap(4, 3); + int32_t outStride = 3 * 4; + int32_t outStrideSwap = 4 * 4; + IntRect dirty; + + auto func = ReorientRow(Orientation()); + dirty = func(in_row0, 0, out, outSize, outStride); + EXPECT_EQ(dirty, IntRect(0, 0, 3, 1)); + dirty = func(in_row1, 1, out, outSize, outStride); + EXPECT_EQ(dirty, IntRect(0, 1, 3, 1)); + dirty = func(in_row2, 2, out, outSize, outStride); + EXPECT_EQ(dirty, IntRect(0, 2, 3, 1)); + dirty = func(in_row3, 3, out, outSize, outStride); + EXPECT_EQ(dirty, IntRect(0, 3, 3, 1)); + + // clang-format off + const uint8_t check_identity[3 * 4 * 4] = { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, + 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, + 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, + 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, + }; + // clang-format on + EXPECT_TRUE(ArrayEqual(out, check_identity)); + + func = ReorientRow(Orientation(Angle::D90)); + dirty = func(in_row0, 0, out, outSizeSwap, outStrideSwap); + EXPECT_EQ(dirty, IntRect(3, 0, 1, 3)); + dirty = func(in_row1, 1, out, outSizeSwap, outStrideSwap); + EXPECT_EQ(dirty, IntRect(2, 0, 1, 3)); + dirty = func(in_row2, 2, out, outSizeSwap, outStrideSwap); + EXPECT_EQ(dirty, IntRect(1, 0, 1, 3)); + dirty = func(in_row3, 3, out, outSizeSwap, outStrideSwap); + EXPECT_EQ(dirty, IntRect(0, 0, 1, 3)); + + // clang-format off + const uint8_t check_d90[3 * 4 * 4] = { + 36, 37, 38, 39, 24, 25, 26, 27, 12, 13, 14, 15, 0, 1, 2, 3, + 40, 41, 42, 43, 28, 29, 30, 31, 16, 17, 18, 19, 4, 5, 6, 7, + 44, 45, 46, 47, 32, 33, 34, 35, 20, 21, 22, 23, 8, 9, 10, 11, + }; + // clang-format on + EXPECT_TRUE(ArrayEqual(out, check_d90)); + + func = ReorientRow(Orientation(Angle::D180)); + dirty = func(in_row0, 0, out, outSize, outStride); + EXPECT_EQ(dirty, IntRect(0, 3, 3, 1)); + dirty = func(in_row1, 1, out, outSize, outStride); + EXPECT_EQ(dirty, IntRect(0, 2, 3, 1)); + dirty = func(in_row2, 2, out, outSize, outStride); + EXPECT_EQ(dirty, IntRect(0, 1, 3, 1)); + dirty = func(in_row3, 3, out, outSize, outStride); + EXPECT_EQ(dirty, IntRect(0, 0, 3, 1)); + + // clang-format off + const uint8_t check_d180[3 * 4 * 4] = { + 44, 45, 46, 47, 40, 41, 42, 43, 36, 37, 38, 39, + 32, 33, 34, 35, 28, 29, 30, 31, 24, 25, 26, 27, + 20, 21, 22, 23, 16, 17, 18, 19, 12, 13, 14, 15, + 8, 9, 10, 11, 4, 5, 6, 7, 0, 1, 2, 3, + }; + // clang-format on + EXPECT_TRUE(ArrayEqual(out, check_d180)); + + func = ReorientRow(Orientation(Angle::D270)); + dirty = func(in_row0, 0, out, outSizeSwap, outStrideSwap); + EXPECT_EQ(dirty, IntRect(0, 0, 1, 3)); + dirty = func(in_row1, 1, out, outSizeSwap, outStrideSwap); + EXPECT_EQ(dirty, IntRect(1, 0, 1, 3)); + dirty = func(in_row2, 2, out, outSizeSwap, outStrideSwap); + EXPECT_EQ(dirty, IntRect(2, 0, 1, 3)); + dirty = func(in_row3, 3, out, outSizeSwap, outStrideSwap); + EXPECT_EQ(dirty, IntRect(3, 0, 1, 3)); + + // clang-format off + const uint8_t check_d270[3 * 4 * 4] = { + 8, 9, 10, 11, 20, 21, 22, 23, 32, 33, 34, 35, 44, 45, 46, 47, + 4, 5, 6, 7, 16, 17, 18, 19, 28, 29, 30, 31, 40, 41, 42, 43, + 0, 1, 2, 3, 12, 13, 14, 15, 24, 25, 26, 27, 36, 37, 38, 39, + }; + // clang-format on + EXPECT_TRUE(ArrayEqual(out, check_d270)); + + func = ReorientRow(Orientation(Angle::D0, Flip::Horizontal)); + dirty = func(in_row0, 0, out, outSize, outStride); + EXPECT_EQ(dirty, IntRect(0, 0, 3, 1)); + dirty = func(in_row1, 1, out, outSize, outStride); + EXPECT_EQ(dirty, IntRect(0, 1, 3, 1)); + dirty = func(in_row2, 2, out, outSize, outStride); + EXPECT_EQ(dirty, IntRect(0, 2, 3, 1)); + dirty = func(in_row3, 3, out, outSize, outStride); + EXPECT_EQ(dirty, IntRect(0, 3, 3, 1)); + + // clang-format off + const uint8_t check_d0_flip[3 * 4 * 4] = { + 8, 9, 10, 11, 4, 5, 6, 7, 0, 1, 2, 3, + 20, 21, 22, 23, 16, 17, 18, 19, 12, 13, 14, 15, + 32, 33, 34, 35, 28, 29, 30, 31, 24, 25, 26, 27, + 44, 45, 46, 47, 40, 41, 42, 43, 36, 37, 38, 39, + }; + // clang-format on + EXPECT_TRUE(ArrayEqual(out, check_d0_flip)); + + func = ReorientRow(Orientation(Angle::D90, Flip::Horizontal)); + dirty = func(in_row0, 0, out, outSizeSwap, outStrideSwap); + EXPECT_EQ(dirty, IntRect(0, 0, 1, 3)); + dirty = func(in_row1, 1, out, outSizeSwap, outStrideSwap); + EXPECT_EQ(dirty, IntRect(1, 0, 1, 3)); + dirty = func(in_row2, 2, out, outSizeSwap, outStrideSwap); + EXPECT_EQ(dirty, IntRect(2, 0, 1, 3)); + dirty = func(in_row3, 3, out, outSizeSwap, outStrideSwap); + EXPECT_EQ(dirty, IntRect(3, 0, 1, 3)); + + // clang-format off + const uint8_t check_d90_flip[3 * 4 * 4] = { + 0, 1, 2, 3, 12, 13, 14, 15, 24, 25, 26, 27, 36, 37, 38, 39, + 4, 5, 6, 7, 16, 17, 18, 19, 28, 29, 30, 31, 40, 41, 42, 43, + 8, 9, 10, 11, 20, 21, 22, 23, 32, 33, 34, 35, 44, 45, 46, 47, + }; + // clang-format on + EXPECT_TRUE(ArrayEqual(out, check_d90_flip)); + + func = ReorientRow(Orientation(Angle::D180, Flip::Horizontal)); + dirty = func(in_row0, 0, out, outSize, outStride); + EXPECT_EQ(dirty, IntRect(0, 3, 3, 1)); + dirty = func(in_row1, 1, out, outSize, outStride); + EXPECT_EQ(dirty, IntRect(0, 2, 3, 1)); + dirty = func(in_row2, 2, out, outSize, outStride); + EXPECT_EQ(dirty, IntRect(0, 1, 3, 1)); + dirty = func(in_row3, 3, out, outSize, outStride); + EXPECT_EQ(dirty, IntRect(0, 0, 3, 1)); + + // clang-format off + const uint8_t check_d180_flip[3 * 4 * 4] = { + 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, + 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, + 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, + }; + // clang-format on + EXPECT_TRUE(ArrayEqual(out, check_d180_flip)); + + func = ReorientRow(Orientation(Angle::D270, Flip::Horizontal)); + dirty = func(in_row0, 0, out, outSizeSwap, outStrideSwap); + EXPECT_EQ(dirty, IntRect(3, 0, 1, 3)); + dirty = func(in_row1, 1, out, outSizeSwap, outStrideSwap); + EXPECT_EQ(dirty, IntRect(2, 0, 1, 3)); + dirty = func(in_row2, 2, out, outSizeSwap, outStrideSwap); + EXPECT_EQ(dirty, IntRect(1, 0, 1, 3)); + dirty = func(in_row3, 3, out, outSizeSwap, outStrideSwap); + EXPECT_EQ(dirty, IntRect(0, 0, 1, 3)); + + // clang-format off + const uint8_t check_d270_flip[3 * 4 * 4] = { + 44, 45, 46, 47, 32, 33, 34, 35, 20, 21, 22, 23, 8, 9, 10, 11, + 40, 41, 42, 43, 28, 29, 30, 31, 16, 17, 18, 19, 4, 5, 6, 7, + 36, 37, 38, 39, 24, 25, 26, 27, 12, 13, 14, 15, 0, 1, 2, 3, + }; + // clang-format on + EXPECT_TRUE(ArrayEqual(out, check_d270_flip)); + + func = ReorientRow( + Orientation(Angle::D0, Flip::Horizontal, /* aFlipFirst */ true)); + dirty = func(in_row0, 0, out, outSize, outStride); + EXPECT_EQ(dirty, IntRect(0, 0, 3, 1)); + dirty = func(in_row1, 1, out, outSize, outStride); + EXPECT_EQ(dirty, IntRect(0, 1, 3, 1)); + dirty = func(in_row2, 2, out, outSize, outStride); + EXPECT_EQ(dirty, IntRect(0, 2, 3, 1)); + dirty = func(in_row3, 3, out, outSize, outStride); + EXPECT_EQ(dirty, IntRect(0, 3, 3, 1)); + + // No rotation, so flipping before and after are the same. + EXPECT_TRUE(ArrayEqual(out, check_d0_flip)); + + func = ReorientRow( + Orientation(Angle::D90, Flip::Horizontal, /* aFlipFirst */ true)); + dirty = func(in_row0, 0, out, outSizeSwap, outStrideSwap); + EXPECT_EQ(dirty, IntRect(3, 0, 1, 3)); + dirty = func(in_row1, 1, out, outSizeSwap, outStrideSwap); + EXPECT_EQ(dirty, IntRect(2, 0, 1, 3)); + dirty = func(in_row2, 2, out, outSizeSwap, outStrideSwap); + EXPECT_EQ(dirty, IntRect(1, 0, 1, 3)); + dirty = func(in_row3, 3, out, outSizeSwap, outStrideSwap); + EXPECT_EQ(dirty, IntRect(0, 0, 1, 3)); + + // Flip, rotate 90 degrees is the same as rotate 270 degrees, flip. + EXPECT_TRUE(ArrayEqual(out, check_d270_flip)); + + func = ReorientRow( + Orientation(Angle::D180, Flip::Horizontal, /* aFlipFirst */ true)); + dirty = func(in_row0, 0, out, outSize, outStride); + EXPECT_EQ(dirty, IntRect(0, 3, 3, 1)); + dirty = func(in_row1, 1, out, outSize, outStride); + EXPECT_EQ(dirty, IntRect(0, 2, 3, 1)); + dirty = func(in_row2, 2, out, outSize, outStride); + EXPECT_EQ(dirty, IntRect(0, 1, 3, 1)); + dirty = func(in_row3, 3, out, outSize, outStride); + EXPECT_EQ(dirty, IntRect(0, 0, 3, 1)); + + // Flip, rotate 180 degrees is the same as rotate 180 degrees, flip. + EXPECT_TRUE(ArrayEqual(out, check_d180_flip)); + + func = ReorientRow( + Orientation(Angle::D270, Flip::Horizontal, /* aFlipFirst */ true)); + dirty = func(in_row0, 0, out, outSizeSwap, outStrideSwap); + EXPECT_EQ(dirty, IntRect(0, 0, 1, 3)); + dirty = func(in_row1, 1, out, outSizeSwap, outStrideSwap); + EXPECT_EQ(dirty, IntRect(1, 0, 1, 3)); + dirty = func(in_row2, 2, out, outSizeSwap, outStrideSwap); + EXPECT_EQ(dirty, IntRect(2, 0, 1, 3)); + dirty = func(in_row3, 3, out, outSizeSwap, outStrideSwap); + EXPECT_EQ(dirty, IntRect(3, 0, 1, 3)); + + // Flip, rotate 270 degrees is the same as rotate 90 degrees, flip. + EXPECT_TRUE(ArrayEqual(out, check_d90_flip)); +} diff --git a/gfx/tests/gtest/TestTextures.cpp b/gfx/tests/gtest/TestTextures.cpp new file mode 100644 index 0000000000..da129a3486 --- /dev/null +++ b/gfx/tests/gtest/TestTextures.cpp @@ -0,0 +1,287 @@ +/* vim:set ts=2 sw=2 sts=2 et: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +#include "gtest/gtest.h" +#include "gmock/gmock.h" + +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/Tools.h" +#include "mozilla/layers/BufferTexture.h" +#include "mozilla/layers/ImageBridgeChild.h" // for ImageBridgeChild +#include "mozilla/layers/TextureClient.h" +#include "mozilla/layers/TextureHost.h" +#include "mozilla/RefPtr.h" +#include "gfx2DGlue.h" +#include "gfxImageSurface.h" +#include "gfxPlatform.h" +#include "gfxTypes.h" +#include "ImageContainer.h" +#include "mozilla/layers/ImageDataSerializer.h" + +using namespace mozilla; +using namespace mozilla::gfx; +using namespace mozilla::layers; + +/* + * This test performs the following actions: + * - creates a surface + * - initialize a texture client with it + * - serilaizes the texture client + * - deserializes the data into a texture host + * - reads the surface from the texture host. + * + * The surface in the end should be equal to the inital one. + * This test is run for different combinations of texture types and + * image formats. + */ + +namespace mozilla { +namespace layers { + +class TestSurfaceAllocator final : public ISurfaceAllocator { + public: + TestSurfaceAllocator() = default; + virtual ~TestSurfaceAllocator() = default; + + bool IsSameProcess() const override { return true; } +}; + +// fills the surface with values betwee 0 and 100. +static void SetupSurface(gfxImageSurface* surface) { + int bpp = gfxASurface::BytePerPixelFromFormat(surface->Format()); + int stride = surface->Stride(); + uint8_t val = 0; + uint8_t* data = surface->Data(); + for (int y = 0; y < surface->Height(); ++y) { + for (int x = 0; x < surface->Height(); ++x) { + for (int b = 0; b < bpp; ++b) { + data[y * stride + x * bpp + b] = val; + if (val == 100) { + val = 0; + } else { + ++val; + } + } + } + } +} + +// return true if two surfaces contain the same data +static void AssertSurfacesEqual(gfxImageSurface* surface1, + gfxImageSurface* surface2) { + ASSERT_EQ(surface1->GetSize(), surface2->GetSize()); + ASSERT_EQ(surface1->Format(), surface2->Format()); + + uint8_t* data1 = surface1->Data(); + uint8_t* data2 = surface2->Data(); + int stride1 = surface1->Stride(); + int stride2 = surface2->Stride(); + int bpp = gfxASurface::BytePerPixelFromFormat(surface1->Format()); + + for (int y = 0; y < surface1->Height(); ++y) { + for (int x = 0; x < surface1->Width(); ++x) { + for (int b = 0; b < bpp; ++b) { + ASSERT_EQ(data1[y * stride1 + x * bpp + b], + data2[y * stride2 + x * bpp + b]); + } + } + } +} + +static void AssertSurfacesEqual(SourceSurface* surface1, + SourceSurface* surface2) { + ASSERT_EQ(surface1->GetSize(), surface2->GetSize()); + ASSERT_EQ(surface1->GetFormat(), surface2->GetFormat()); + + RefPtr<DataSourceSurface> dataSurface1 = surface1->GetDataSurface(); + RefPtr<DataSourceSurface> dataSurface2 = surface2->GetDataSurface(); + DataSourceSurface::MappedSurface map1; + DataSourceSurface::MappedSurface map2; + if (!dataSurface1->Map(DataSourceSurface::READ, &map1)) { + return; + } + if (!dataSurface2->Map(DataSourceSurface::READ, &map2)) { + dataSurface1->Unmap(); + return; + } + uint8_t* data1 = map1.mData; + uint8_t* data2 = map2.mData; + int stride1 = map1.mStride; + int stride2 = map2.mStride; + int bpp = BytesPerPixel(surface1->GetFormat()); + int width = surface1->GetSize().width; + int height = surface1->GetSize().height; + + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { + for (int b = 0; b < bpp; ++b) { + ASSERT_EQ(data1[y * stride1 + x * bpp + b], + data2[y * stride2 + x * bpp + b]); + } + } + } + + dataSurface1->Unmap(); + dataSurface2->Unmap(); +} + +// Run the test for a texture client and a surface +void TestTextureClientSurface(TextureClient* texture, + gfxImageSurface* surface) { + // client allocation + ASSERT_TRUE(texture->CanExposeDrawTarget()); + + ASSERT_TRUE(texture->Lock(OpenMode::OPEN_READ_WRITE)); + // client painting + RefPtr<DrawTarget> dt = texture->BorrowDrawTarget(); + RefPtr<SourceSurface> source = + gfxPlatform::GetPlatform()->GetSourceSurfaceForSurface(dt, surface); + dt->CopySurface(source, IntRect(IntPoint(), source->GetSize()), IntPoint()); + + RefPtr<SourceSurface> snapshot = dt->Snapshot(); + + AssertSurfacesEqual(snapshot, source); + + dt = nullptr; // drop reference before calling Unlock() + texture->Unlock(); + + // client serialization + SurfaceDescriptor descriptor; + ASSERT_TRUE(texture->ToSurfaceDescriptor(descriptor)); + + ASSERT_NE(descriptor.type(), SurfaceDescriptor::Tnull_t); + + // host deserialization + RefPtr<TestSurfaceAllocator> deallocator = new TestSurfaceAllocator(); + RefPtr<TextureHost> host = CreateBackendIndependentTextureHost( + descriptor, deallocator, LayersBackend::LAYERS_NONE, texture->GetFlags()); + + ASSERT_TRUE(host.get() != nullptr); + ASSERT_EQ(host->GetFlags(), texture->GetFlags()); +} + +// Same as above, for YCbCr surfaces +void TestTextureClientYCbCr(TextureClient* client, PlanarYCbCrData& ycbcrData) { + client->Lock(OpenMode::OPEN_READ_WRITE); + UpdateYCbCrTextureClient(client, ycbcrData); + client->Unlock(); + + // client serialization + SurfaceDescriptor descriptor; + ASSERT_TRUE(client->ToSurfaceDescriptor(descriptor)); + + ASSERT_EQ(descriptor.type(), SurfaceDescriptor::TSurfaceDescriptorBuffer); + auto bufferDesc = descriptor.get_SurfaceDescriptorBuffer(); + ASSERT_EQ(bufferDesc.desc().type(), BufferDescriptor::TYCbCrDescriptor); + auto ycbcrDesc = bufferDesc.desc().get_YCbCrDescriptor(); + ASSERT_EQ(ycbcrDesc.ySize(), ycbcrData.YDataSize()); + ASSERT_EQ(ycbcrDesc.cbCrSize(), ycbcrData.CbCrDataSize()); + ASSERT_EQ(ycbcrDesc.stereoMode(), ycbcrData.mStereoMode); + + // host deserialization + RefPtr<TestSurfaceAllocator> deallocator = new TestSurfaceAllocator(); + RefPtr<TextureHost> textureHost = CreateBackendIndependentTextureHost( + descriptor, deallocator, LayersBackend::LAYERS_NONE, client->GetFlags()); + + RefPtr<BufferTextureHost> host = + static_cast<BufferTextureHost*>(textureHost.get()); + + ASSERT_TRUE(host.get() != nullptr); + ASSERT_EQ(host->GetFlags(), client->GetFlags()); +} + +} // namespace layers +} // namespace mozilla + +TEST(Layers, TextureSerialization) +{ + // the test is run on all the following image formats + gfxImageFormat formats[3] = { + SurfaceFormat::A8R8G8B8_UINT32, + SurfaceFormat::X8R8G8B8_UINT32, + SurfaceFormat::A8, + }; + + for (int f = 0; f < 3; ++f) { + RefPtr<gfxImageSurface> surface = + new gfxImageSurface(IntSize(400, 300), formats[f]); + SetupSurface(surface.get()); + AssertSurfacesEqual(surface, surface); + + auto texData = BufferTextureData::Create( + surface->GetSize(), gfx::ImageFormatToSurfaceFormat(surface->Format()), + gfx::BackendType::CAIRO, LayersBackend::LAYERS_NONE, + TextureFlags::DEALLOCATE_CLIENT, ALLOC_DEFAULT, nullptr); + ASSERT_TRUE(!!texData); + + RefPtr<TextureClient> client = + new TextureClient(texData, TextureFlags::DEALLOCATE_CLIENT, nullptr); + + TestTextureClientSurface(client, surface); + + // XXX - Test more texture client types. + } +} + +TEST(Layers, TextureYCbCrSerialization) +{ + RefPtr<gfxImageSurface> ySurface = + new gfxImageSurface(IntSize(400, 300), SurfaceFormat::A8); + RefPtr<gfxImageSurface> cbSurface = + new gfxImageSurface(IntSize(200, 150), SurfaceFormat::A8); + RefPtr<gfxImageSurface> crSurface = + new gfxImageSurface(IntSize(200, 150), SurfaceFormat::A8); + SetupSurface(ySurface.get()); + SetupSurface(cbSurface.get()); + SetupSurface(crSurface.get()); + + PlanarYCbCrData clientData; + clientData.mYChannel = ySurface->Data(); + clientData.mCbChannel = cbSurface->Data(); + clientData.mCrChannel = crSurface->Data(); + clientData.mPictureRect = IntRect(IntPoint(0, 0), ySurface->GetSize()); + clientData.mYStride = ySurface->Stride(); + clientData.mCbCrStride = cbSurface->Stride(); + clientData.mStereoMode = StereoMode::MONO; + clientData.mYUVColorSpace = YUVColorSpace::BT601; + clientData.mColorDepth = ColorDepth::COLOR_8; + clientData.mChromaSubsampling = ChromaSubsampling::HALF_WIDTH_AND_HEIGHT; + clientData.mYSkip = 0; + clientData.mCbSkip = 0; + clientData.mCrSkip = 0; + clientData.mCrSkip = 0; + + uint32_t namespaceId = 1; + ImageBridgeChild::InitSameProcess(namespaceId); + + RefPtr<ImageBridgeChild> imageBridge = ImageBridgeChild::GetSingleton(); + static int retry = 5; + while (!imageBridge->IPCOpen() && retry) { + // IPDL connection takes time especially in slow testing environment, like + // VM machines. Here we added retry mechanism to wait for IPDL connnection. +#ifdef XP_WIN + Sleep(1); +#else + sleep(1); +#endif + retry--; + } + + // Skip this testing if IPDL connection is not ready + if (!retry && !imageBridge->IPCOpen()) { + return; + } + + RefPtr<TextureClient> client = TextureClient::CreateForYCbCr( + imageBridge, clientData.mPictureRect, clientData.YDataSize(), + clientData.mYStride, clientData.CbCrDataSize(), clientData.mCbCrStride, + StereoMode::MONO, ColorDepth::COLOR_8, YUVColorSpace::BT601, + ColorRange::LIMITED, clientData.mChromaSubsampling, + TextureFlags::DEALLOCATE_CLIENT); + + TestTextureClientYCbCr(client, clientData); + + // XXX - Test more texture client types. +} diff --git a/gfx/tests/gtest/TestTreeTraversal.cpp b/gfx/tests/gtest/TestTreeTraversal.cpp new file mode 100644 index 0000000000..7e538ca514 --- /dev/null +++ b/gfx/tests/gtest/TestTreeTraversal.cpp @@ -0,0 +1,1408 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <vector> +#include "mozilla/RefPtr.h" +#include "gtest/gtest.h" +#include "nsRegion.h" +#include "nsRect.h" +#include "TreeTraversal.h" +#include <stack> +#include <queue> + +using namespace mozilla::layers; +using namespace mozilla; + +enum class SearchNodeType { Needle, Hay }; +enum class ForEachNodeType { Continue, Skip }; + +template <class T> +class TestNodeBase { + public: + NS_INLINE_DECL_REFCOUNTING(TestNodeBase<T>); + explicit TestNodeBase(T aType, int aExpectedTraversalRank = -1); + explicit TestNodeBase(); + void SetActualTraversalRank(int aRank); + void SetValue(int aValue); + void SetType(T aType); + void SetRegion(nsRegion aRegion); + int GetExpectedTraversalRank(); + int GetActualTraversalRank(); + int GetValue(); + T GetType(); + nsRegion GetRegion(); + virtual bool IsLeaf() = 0; + + private: + MOZ_INIT_OUTSIDE_CTOR int mExpectedTraversalRank; + MOZ_INIT_OUTSIDE_CTOR int mActualTraversalRank; + MOZ_INIT_OUTSIDE_CTOR int mValue; + MOZ_INIT_OUTSIDE_CTOR nsRegion mRegion; + MOZ_INIT_OUTSIDE_CTOR T mType; + + protected: + virtual ~TestNodeBase() = default; +}; + +template <class T> +class TestNodeReverse : public TestNodeBase<T> { + public: + explicit TestNodeReverse(T aType, int aExpectedTraversalRank = -1); + explicit TestNodeReverse(); + void AddChild(RefPtr<TestNodeReverse<T>> aNode); + TestNodeReverse<T>* GetLastChild(); + TestNodeReverse<T>* GetPrevSibling(); + bool IsLeaf(); + + private: + void SetPrevSibling(RefPtr<TestNodeReverse<T>> aNode); + void SetLastChild(RefPtr<TestNodeReverse<T>> aNode); + RefPtr<TestNodeReverse<T>> mSiblingNode; + RefPtr<TestNodeReverse<T>> mLastChildNode; + ~TestNodeReverse() = default; +}; + +template <class T> +class TestNodeForward : public TestNodeBase<T> { + public: + explicit TestNodeForward(T aType, int aExpectedTraversalRank = -1); + explicit TestNodeForward(); + void AddChild(RefPtr<TestNodeForward<T>> aNode); + TestNodeForward<T>* GetFirstChild(); + TestNodeForward<T>* GetNextSibling(); + bool IsLeaf(); + + private: + void SetNextSibling(RefPtr<TestNodeForward<T>> aNode); + void SetLastChild(RefPtr<TestNodeForward<T>> aNode); + void SetFirstChild(RefPtr<TestNodeForward<T>> aNode); + RefPtr<TestNodeForward<T>> mSiblingNode = nullptr; + RefPtr<TestNodeForward<T>> mFirstChildNode = nullptr; + // Track last child to facilitate appending children + RefPtr<TestNodeForward<T>> mLastChildNode = nullptr; + ~TestNodeForward() = default; +}; + +template <class T> +TestNodeReverse<T>::TestNodeReverse(T aType, int aExpectedTraversalRank) + : TestNodeBase<T>(aType, aExpectedTraversalRank) {} + +template <class T> +TestNodeReverse<T>::TestNodeReverse() : TestNodeBase<T>() {} + +template <class T> +void TestNodeReverse<T>::SetLastChild(RefPtr<TestNodeReverse<T>> aNode) { + mLastChildNode = aNode; +} + +template <class T> +void TestNodeReverse<T>::AddChild(RefPtr<TestNodeReverse<T>> aNode) { + aNode->SetPrevSibling(mLastChildNode); + SetLastChild(aNode); +} + +template <class T> +void TestNodeReverse<T>::SetPrevSibling(RefPtr<TestNodeReverse<T>> aNode) { + mSiblingNode = aNode; +} + +template <class T> +TestNodeReverse<T>* TestNodeReverse<T>::GetLastChild() { + return mLastChildNode; +} + +template <class T> +TestNodeReverse<T>* TestNodeReverse<T>::GetPrevSibling() { + return mSiblingNode; +} + +template <class T> +bool TestNodeReverse<T>::IsLeaf() { + return !mLastChildNode; +} + +template <class T> +TestNodeForward<T>::TestNodeForward(T aType, int aExpectedTraversalRank) + : TestNodeBase<T>(aType, aExpectedTraversalRank) {} + +template <class T> +TestNodeForward<T>::TestNodeForward() : TestNodeBase<T>() {} + +template <class T> +void TestNodeForward<T>::AddChild(RefPtr<TestNodeForward<T>> aNode) { + if (mFirstChildNode == nullptr) { + SetFirstChild(aNode); + SetLastChild(aNode); + } else { + mLastChildNode->SetNextSibling(aNode); + SetLastChild(aNode); + } +} + +template <class T> +void TestNodeForward<T>::SetLastChild(RefPtr<TestNodeForward<T>> aNode) { + mLastChildNode = aNode; +} + +template <class T> +void TestNodeForward<T>::SetFirstChild(RefPtr<TestNodeForward<T>> aNode) { + mFirstChildNode = aNode; +} + +template <class T> +void TestNodeForward<T>::SetNextSibling(RefPtr<TestNodeForward<T>> aNode) { + mSiblingNode = aNode; +} + +template <class T> +bool TestNodeForward<T>::IsLeaf() { + return !mFirstChildNode; +} + +template <class T> +TestNodeForward<T>* TestNodeForward<T>::GetFirstChild() { + return mFirstChildNode; +} + +template <class T> +TestNodeForward<T>* TestNodeForward<T>::GetNextSibling() { + return mSiblingNode; +} + +template <class T> +TestNodeBase<T>::TestNodeBase(T aType, int aExpectedTraversalRank) + : mExpectedTraversalRank(aExpectedTraversalRank), + mActualTraversalRank(-1), + mType(aType) {} + +template <class T> +TestNodeBase<T>::TestNodeBase() = default; + +template <class T> +int TestNodeBase<T>::GetActualTraversalRank() { + return mActualTraversalRank; +} + +template <class T> +void TestNodeBase<T>::SetActualTraversalRank(int aRank) { + mActualTraversalRank = aRank; +} + +template <class T> +int TestNodeBase<T>::GetExpectedTraversalRank() { + return mExpectedTraversalRank; +} + +template <class T> +T TestNodeBase<T>::GetType() { + return mType; +} + +template <class T> +void TestNodeBase<T>::SetType(T aType) { + mType = aType; +} + +template <class T> +nsRegion TestNodeBase<T>::GetRegion() { + return mRegion; +} + +template <class T> +void TestNodeBase<T>::SetRegion(nsRegion aRegion) { + mRegion = aRegion; +} + +template <class T> +int TestNodeBase<T>::GetValue() { + return mValue; +} + +template <class T> +void TestNodeBase<T>::SetValue(int aValue) { + mValue = aValue; +} + +typedef TestNodeBase<SearchNodeType> SearchTestNode; +typedef TestNodeBase<ForEachNodeType> ForEachTestNode; +typedef TestNodeReverse<SearchNodeType> SearchTestNodeReverse; +typedef TestNodeReverse<ForEachNodeType> ForEachTestNodeReverse; +typedef TestNodeForward<SearchNodeType> SearchTestNodeForward; +typedef TestNodeForward<ForEachNodeType> ForEachTestNodeForward; + +TEST(TreeTraversal, DepthFirstSearchNull) +{ + RefPtr<SearchTestNodeReverse> nullNode; + RefPtr<SearchTestNodeReverse> result = + DepthFirstSearch<layers::ReverseIterator>( + nullNode.get(), [](SearchTestNodeReverse* aNode) { + return aNode->GetType() == SearchNodeType::Needle; + }); + ASSERT_EQ(result.get(), nullptr) + << "Null root did not return null search result."; +} + +TEST(TreeTraversal, DepthFirstSearchValueExists) +{ + int visitCount = 0; + size_t expectedNeedleTraversalRank = 7; + RefPtr<SearchTestNodeForward> needleNode; + std::vector<RefPtr<SearchTestNodeForward>> nodeList; + nodeList.reserve(10); + for (size_t i = 0; i < 10; i++) { + if (i == expectedNeedleTraversalRank) { + needleNode = new SearchTestNodeForward(SearchNodeType::Needle, i); + nodeList.push_back(needleNode); + } else if (i < expectedNeedleTraversalRank) { + nodeList.push_back(new SearchTestNodeForward(SearchNodeType::Hay, i)); + } else { + nodeList.push_back(new SearchTestNodeForward(SearchNodeType::Hay)); + } + } + + RefPtr<SearchTestNodeForward> root = nodeList[0]; + nodeList[0]->AddChild(nodeList[1]); + nodeList[0]->AddChild(nodeList[4]); + nodeList[1]->AddChild(nodeList[2]); + nodeList[1]->AddChild(nodeList[3]); + nodeList[4]->AddChild(nodeList[5]); + nodeList[4]->AddChild(nodeList[6]); + nodeList[6]->AddChild(nodeList[7]); + nodeList[7]->AddChild(nodeList[8]); + nodeList[7]->AddChild(nodeList[9]); + + RefPtr<SearchTestNodeForward> foundNode = + DepthFirstSearch<layers::ForwardIterator>( + root.get(), [&visitCount](SearchTestNodeForward* aNode) { + aNode->SetActualTraversalRank(visitCount); + visitCount++; + return aNode->GetType() == SearchNodeType::Needle; + }); + + for (size_t i = 0; i < nodeList.size(); i++) { + ASSERT_EQ(nodeList[i]->GetExpectedTraversalRank(), + nodeList[i]->GetActualTraversalRank()) + << "Node at index " << i << " was hit out of order."; + } + + ASSERT_EQ(foundNode, needleNode) << "Search did not return expected node."; + ASSERT_EQ(foundNode->GetType(), SearchNodeType::Needle) + << "Returned node does not match expected value (something odd " + "happened)."; +} + +TEST(TreeTraversal, DepthFirstSearchValueExistsReverse) +{ + int visitCount = 0; + size_t expectedNeedleTraversalRank = 7; + RefPtr<SearchTestNodeReverse> needleNode; + std::vector<RefPtr<SearchTestNodeReverse>> nodeList; + nodeList.reserve(10); + for (size_t i = 0; i < 10; i++) { + if (i == expectedNeedleTraversalRank) { + needleNode = new SearchTestNodeReverse(SearchNodeType::Needle, i); + nodeList.push_back(needleNode); + } else if (i < expectedNeedleTraversalRank) { + nodeList.push_back(new SearchTestNodeReverse(SearchNodeType::Hay, i)); + } else { + nodeList.push_back(new SearchTestNodeReverse(SearchNodeType::Hay)); + } + } + + RefPtr<SearchTestNodeReverse> root = nodeList[0]; + nodeList[0]->AddChild(nodeList[4]); + nodeList[0]->AddChild(nodeList[1]); + nodeList[1]->AddChild(nodeList[3]); + nodeList[1]->AddChild(nodeList[2]); + nodeList[4]->AddChild(nodeList[6]); + nodeList[4]->AddChild(nodeList[5]); + nodeList[6]->AddChild(nodeList[7]); + nodeList[7]->AddChild(nodeList[9]); + nodeList[7]->AddChild(nodeList[8]); + + RefPtr<SearchTestNodeReverse> foundNode = + DepthFirstSearch<layers::ReverseIterator>( + root.get(), [&visitCount](SearchTestNodeReverse* aNode) { + aNode->SetActualTraversalRank(visitCount); + visitCount++; + return aNode->GetType() == SearchNodeType::Needle; + }); + + for (size_t i = 0; i < nodeList.size(); i++) { + ASSERT_EQ(nodeList[i]->GetExpectedTraversalRank(), + nodeList[i]->GetActualTraversalRank()) + << "Node at index " << i << " was hit out of order."; + } + + ASSERT_EQ(foundNode, needleNode) << "Search did not return expected node."; + ASSERT_EQ(foundNode->GetType(), SearchNodeType::Needle) + << "Returned node does not match expected value (something odd " + "happened)."; +} + +TEST(TreeTraversal, DepthFirstSearchRootIsNeedle) +{ + RefPtr<SearchTestNodeReverse> root = + new SearchTestNodeReverse(SearchNodeType::Needle, 0); + RefPtr<SearchTestNodeReverse> childNode1 = + new SearchTestNodeReverse(SearchNodeType::Hay); + RefPtr<SearchTestNodeReverse> childNode2 = + new SearchTestNodeReverse(SearchNodeType::Hay); + int visitCount = 0; + RefPtr<SearchTestNodeReverse> result = + DepthFirstSearch<layers::ReverseIterator>( + root.get(), [&visitCount](SearchTestNodeReverse* aNode) { + aNode->SetActualTraversalRank(visitCount); + visitCount++; + return aNode->GetType() == SearchNodeType::Needle; + }); + ASSERT_EQ(result, root) << "Search starting at needle did not return needle."; + ASSERT_EQ(root->GetExpectedTraversalRank(), root->GetActualTraversalRank()) + << "Search starting at needle did not return needle."; + ASSERT_EQ(childNode1->GetExpectedTraversalRank(), + childNode1->GetActualTraversalRank()) + << "Search starting at needle continued past needle."; + ASSERT_EQ(childNode2->GetExpectedTraversalRank(), + childNode2->GetActualTraversalRank()) + << "Search starting at needle continued past needle."; +} + +TEST(TreeTraversal, DepthFirstSearchValueDoesNotExist) +{ + int visitCount = 0; + std::vector<RefPtr<SearchTestNodeForward>> nodeList; + nodeList.reserve(10); + for (int i = 0; i < 10; i++) { + nodeList.push_back(new SearchTestNodeForward(SearchNodeType::Hay, i)); + } + + RefPtr<SearchTestNodeForward> root = nodeList[0]; + nodeList[0]->AddChild(nodeList[1]); + nodeList[0]->AddChild(nodeList[4]); + nodeList[1]->AddChild(nodeList[2]); + nodeList[1]->AddChild(nodeList[3]); + nodeList[4]->AddChild(nodeList[5]); + nodeList[4]->AddChild(nodeList[6]); + nodeList[6]->AddChild(nodeList[7]); + nodeList[7]->AddChild(nodeList[8]); + nodeList[7]->AddChild(nodeList[9]); + + RefPtr<SearchTestNodeForward> foundNode = + DepthFirstSearch<layers::ForwardIterator>( + root.get(), [&visitCount](SearchTestNodeForward* aNode) { + aNode->SetActualTraversalRank(visitCount); + visitCount++; + return aNode->GetType() == SearchNodeType::Needle; + }); + + for (int i = 0; i < 10; i++) { + ASSERT_EQ(nodeList[i]->GetExpectedTraversalRank(), + nodeList[i]->GetActualTraversalRank()) + << "Node at index " << i << " was hit out of order."; + } + + ASSERT_EQ(foundNode.get(), nullptr) + << "Search found something that should not exist."; +} + +TEST(TreeTraversal, DepthFirstSearchValueDoesNotExistReverse) +{ + int visitCount = 0; + std::vector<RefPtr<SearchTestNodeReverse>> nodeList; + nodeList.reserve(10); + for (int i = 0; i < 10; i++) { + nodeList.push_back(new SearchTestNodeReverse(SearchNodeType::Hay, i)); + } + + RefPtr<SearchTestNodeReverse> root = nodeList[0]; + nodeList[0]->AddChild(nodeList[4]); + nodeList[0]->AddChild(nodeList[1]); + nodeList[1]->AddChild(nodeList[3]); + nodeList[1]->AddChild(nodeList[2]); + nodeList[4]->AddChild(nodeList[6]); + nodeList[4]->AddChild(nodeList[5]); + nodeList[6]->AddChild(nodeList[7]); + nodeList[7]->AddChild(nodeList[9]); + nodeList[7]->AddChild(nodeList[8]); + + RefPtr<SearchTestNodeReverse> foundNode = + DepthFirstSearch<layers::ReverseIterator>( + root.get(), [&visitCount](SearchTestNodeReverse* aNode) { + aNode->SetActualTraversalRank(visitCount); + visitCount++; + return aNode->GetType() == SearchNodeType::Needle; + }); + + for (int i = 0; i < 10; i++) { + ASSERT_EQ(nodeList[i]->GetExpectedTraversalRank(), + nodeList[i]->GetActualTraversalRank()) + << "Node at index " << i << " was hit out of order."; + } + + ASSERT_EQ(foundNode.get(), nullptr) + << "Search found something that should not exist."; +} + +TEST(TreeTraversal, DepthFirstSearchPostOrderNull) +{ + RefPtr<SearchTestNodeReverse> nullNode; + RefPtr<SearchTestNodeReverse> result = + DepthFirstSearchPostOrder<layers::ReverseIterator>( + nullNode.get(), [](SearchTestNodeReverse* aNode) { + return aNode->GetType() == SearchNodeType::Needle; + }); + ASSERT_EQ(result.get(), nullptr) + << "Null root did not return null search result."; +} + +TEST(TreeTraversal, DepthFirstSearchPostOrderValueExists) +{ + int visitCount = 0; + size_t expectedNeedleTraversalRank = 7; + RefPtr<SearchTestNodeForward> needleNode; + std::vector<RefPtr<SearchTestNodeForward>> nodeList; + for (size_t i = 0; i < 10; i++) { + if (i == expectedNeedleTraversalRank) { + needleNode = new SearchTestNodeForward(SearchNodeType::Needle, i); + nodeList.push_back(needleNode); + } else if (i < expectedNeedleTraversalRank) { + nodeList.push_back(new SearchTestNodeForward(SearchNodeType::Hay, i)); + } else { + nodeList.push_back(new SearchTestNodeForward(SearchNodeType::Hay)); + } + } + + RefPtr<SearchTestNodeForward> root = nodeList[9]; + nodeList[9]->AddChild(nodeList[2]); + nodeList[9]->AddChild(nodeList[8]); + nodeList[2]->AddChild(nodeList[0]); + nodeList[2]->AddChild(nodeList[1]); + nodeList[8]->AddChild(nodeList[6]); + nodeList[8]->AddChild(nodeList[7]); + nodeList[6]->AddChild(nodeList[5]); + nodeList[5]->AddChild(nodeList[3]); + nodeList[5]->AddChild(nodeList[4]); + + RefPtr<SearchTestNodeForward> foundNode = + DepthFirstSearchPostOrder<layers::ForwardIterator>( + root.get(), [&visitCount](SearchTestNodeForward* aNode) { + aNode->SetActualTraversalRank(visitCount); + visitCount++; + return aNode->GetType() == SearchNodeType::Needle; + }); + + for (size_t i = 0; i < nodeList.size(); i++) { + ASSERT_EQ(nodeList[i]->GetExpectedTraversalRank(), + nodeList[i]->GetActualTraversalRank()) + << "Node at index " << i << " was hit out of order."; + } + + ASSERT_EQ(foundNode, needleNode) << "Search did not return expected node."; + ASSERT_EQ(foundNode->GetType(), SearchNodeType::Needle) + << "Returned node does not match expected value (something odd " + "happened)."; +} + +TEST(TreeTraversal, DepthFirstSearchPostOrderValueExistsReverse) +{ + int visitCount = 0; + size_t expectedNeedleTraversalRank = 7; + RefPtr<SearchTestNodeReverse> needleNode; + std::vector<RefPtr<SearchTestNodeReverse>> nodeList; + for (size_t i = 0; i < 10; i++) { + if (i == expectedNeedleTraversalRank) { + needleNode = new SearchTestNodeReverse(SearchNodeType::Needle, i); + nodeList.push_back(needleNode); + } else if (i < expectedNeedleTraversalRank) { + nodeList.push_back(new SearchTestNodeReverse(SearchNodeType::Hay, i)); + } else { + nodeList.push_back(new SearchTestNodeReverse(SearchNodeType::Hay)); + } + } + + RefPtr<SearchTestNodeReverse> root = nodeList[9]; + nodeList[9]->AddChild(nodeList[8]); + nodeList[9]->AddChild(nodeList[2]); + nodeList[2]->AddChild(nodeList[1]); + nodeList[2]->AddChild(nodeList[0]); + nodeList[8]->AddChild(nodeList[7]); + nodeList[8]->AddChild(nodeList[6]); + nodeList[6]->AddChild(nodeList[5]); + nodeList[5]->AddChild(nodeList[4]); + nodeList[5]->AddChild(nodeList[3]); + + RefPtr<SearchTestNodeReverse> foundNode = + DepthFirstSearchPostOrder<layers::ReverseIterator>( + root.get(), [&visitCount](SearchTestNodeReverse* aNode) { + aNode->SetActualTraversalRank(visitCount); + visitCount++; + return aNode->GetType() == SearchNodeType::Needle; + }); + + for (size_t i = 0; i < nodeList.size(); i++) { + ASSERT_EQ(nodeList[i]->GetExpectedTraversalRank(), + nodeList[i]->GetActualTraversalRank()) + << "Node at index " << i << " was hit out of order."; + } + + ASSERT_EQ(foundNode, needleNode) << "Search did not return expected node."; + ASSERT_EQ(foundNode->GetType(), SearchNodeType::Needle) + << "Returned node does not match expected value (something odd " + "happened)."; +} + +TEST(TreeTraversal, DepthFirstSearchPostOrderRootIsNeedle) +{ + RefPtr<SearchTestNodeReverse> root = + new SearchTestNodeReverse(SearchNodeType::Needle, 0); + RefPtr<SearchTestNodeReverse> childNode1 = + new SearchTestNodeReverse(SearchNodeType::Hay); + RefPtr<SearchTestNodeReverse> childNode2 = + new SearchTestNodeReverse(SearchNodeType::Hay); + int visitCount = 0; + RefPtr<SearchTestNodeReverse> result = + DepthFirstSearchPostOrder<layers::ReverseIterator>( + root.get(), [&visitCount](SearchTestNodeReverse* aNode) { + aNode->SetActualTraversalRank(visitCount); + visitCount++; + return aNode->GetType() == SearchNodeType::Needle; + }); + ASSERT_EQ(result, root) << "Search starting at needle did not return needle."; + ASSERT_EQ(root->GetExpectedTraversalRank(), root->GetActualTraversalRank()) + << "Search starting at needle did not return needle."; + ASSERT_EQ(childNode1->GetExpectedTraversalRank(), + childNode1->GetActualTraversalRank()) + << "Search starting at needle continued past needle."; + ASSERT_EQ(childNode2->GetExpectedTraversalRank(), + childNode2->GetActualTraversalRank()) + << "Search starting at needle continued past needle."; +} + +TEST(TreeTraversal, DepthFirstSearchPostOrderValueDoesNotExist) +{ + int visitCount = 0; + std::vector<RefPtr<SearchTestNodeForward>> nodeList; + nodeList.reserve(10); + for (int i = 0; i < 10; i++) { + nodeList.push_back(new SearchTestNodeForward(SearchNodeType::Hay, i)); + } + + RefPtr<SearchTestNodeForward> root = nodeList[9]; + nodeList[9]->AddChild(nodeList[2]); + nodeList[9]->AddChild(nodeList[8]); + nodeList[2]->AddChild(nodeList[0]); + nodeList[2]->AddChild(nodeList[1]); + nodeList[8]->AddChild(nodeList[6]); + nodeList[8]->AddChild(nodeList[7]); + nodeList[6]->AddChild(nodeList[5]); + nodeList[5]->AddChild(nodeList[3]); + nodeList[5]->AddChild(nodeList[4]); + + RefPtr<SearchTestNodeForward> foundNode = + DepthFirstSearchPostOrder<layers::ForwardIterator>( + root.get(), [&visitCount](SearchTestNodeForward* aNode) { + aNode->SetActualTraversalRank(visitCount); + visitCount++; + return aNode->GetType() == SearchNodeType::Needle; + }); + + for (int i = 0; i < 10; i++) { + ASSERT_EQ(nodeList[i]->GetExpectedTraversalRank(), + nodeList[i]->GetActualTraversalRank()) + << "Node at index " << i << " was hit out of order."; + } + + ASSERT_EQ(foundNode.get(), nullptr) + << "Search found something that should not exist."; +} + +TEST(TreeTraversal, DepthFirstSearchPostOrderValueDoesNotExistReverse) +{ + int visitCount = 0; + std::vector<RefPtr<SearchTestNodeReverse>> nodeList; + nodeList.reserve(10); + for (int i = 0; i < 10; i++) { + nodeList.push_back(new SearchTestNodeReverse(SearchNodeType::Hay, i)); + } + + RefPtr<SearchTestNodeReverse> root = nodeList[9]; + nodeList[9]->AddChild(nodeList[8]); + nodeList[9]->AddChild(nodeList[2]); + nodeList[2]->AddChild(nodeList[1]); + nodeList[2]->AddChild(nodeList[0]); + nodeList[8]->AddChild(nodeList[7]); + nodeList[8]->AddChild(nodeList[6]); + nodeList[6]->AddChild(nodeList[5]); + nodeList[5]->AddChild(nodeList[4]); + nodeList[5]->AddChild(nodeList[3]); + + RefPtr<SearchTestNodeReverse> foundNode = + DepthFirstSearchPostOrder<layers::ReverseIterator>( + root.get(), [&visitCount](SearchTestNodeReverse* aNode) { + aNode->SetActualTraversalRank(visitCount); + visitCount++; + return aNode->GetType() == SearchNodeType::Needle; + }); + + for (int i = 0; i < 10; i++) { + ASSERT_EQ(nodeList[i]->GetExpectedTraversalRank(), + nodeList[i]->GetActualTraversalRank()) + << "Node at index " << i << " was hit out of order."; + } + + ASSERT_EQ(foundNode.get(), nullptr) + << "Search found something that should not exist."; +} + +TEST(TreeTraversal, BreadthFirstSearchNull) +{ + RefPtr<SearchTestNodeReverse> nullNode; + RefPtr<SearchTestNodeReverse> result = + BreadthFirstSearch<layers::ReverseIterator>( + nullNode.get(), [](SearchTestNodeReverse* aNode) { + return aNode->GetType() == SearchNodeType::Needle; + }); + ASSERT_EQ(result.get(), nullptr) + << "Null root did not return null search result."; +} + +TEST(TreeTraversal, BreadthFirstSearchRootIsNeedle) +{ + RefPtr<SearchTestNodeReverse> root = + new SearchTestNodeReverse(SearchNodeType::Needle, 0); + RefPtr<SearchTestNodeReverse> childNode1 = + new SearchTestNodeReverse(SearchNodeType::Hay); + RefPtr<SearchTestNodeReverse> childNode2 = + new SearchTestNodeReverse(SearchNodeType::Hay); + int visitCount = 0; + RefPtr<SearchTestNodeReverse> result = + BreadthFirstSearch<layers::ReverseIterator>( + root.get(), [&visitCount](SearchTestNodeReverse* aNode) { + aNode->SetActualTraversalRank(visitCount); + visitCount++; + return aNode->GetType() == SearchNodeType::Needle; + }); + ASSERT_EQ(result, root) << "Search starting at needle did not return needle."; + ASSERT_EQ(root->GetExpectedTraversalRank(), root->GetActualTraversalRank()) + << "Search starting at needle did not return needle."; + ASSERT_EQ(childNode1->GetExpectedTraversalRank(), + childNode1->GetActualTraversalRank()) + << "Search starting at needle continued past needle."; + ASSERT_EQ(childNode2->GetExpectedTraversalRank(), + childNode2->GetActualTraversalRank()) + << "Search starting at needle continued past needle."; +} + +TEST(TreeTraversal, BreadthFirstSearchValueExists) +{ + int visitCount = 0; + size_t expectedNeedleTraversalRank = 7; + RefPtr<SearchTestNodeForward> needleNode; + std::vector<RefPtr<SearchTestNodeForward>> nodeList; + nodeList.reserve(10); + for (size_t i = 0; i < 10; i++) { + if (i == expectedNeedleTraversalRank) { + needleNode = new SearchTestNodeForward(SearchNodeType::Needle, i); + nodeList.push_back(needleNode); + } else if (i < expectedNeedleTraversalRank) { + nodeList.push_back(new SearchTestNodeForward(SearchNodeType::Hay, i)); + } else { + nodeList.push_back(new SearchTestNodeForward(SearchNodeType::Hay)); + } + } + + RefPtr<SearchTestNodeForward> root = nodeList[0]; + nodeList[0]->AddChild(nodeList[1]); + nodeList[0]->AddChild(nodeList[2]); + nodeList[1]->AddChild(nodeList[3]); + nodeList[1]->AddChild(nodeList[4]); + nodeList[2]->AddChild(nodeList[5]); + nodeList[2]->AddChild(nodeList[6]); + nodeList[6]->AddChild(nodeList[7]); + nodeList[7]->AddChild(nodeList[8]); + nodeList[7]->AddChild(nodeList[9]); + + RefPtr<SearchTestNodeForward> foundNode = + BreadthFirstSearch<layers::ForwardIterator>( + root.get(), [&visitCount](SearchTestNodeForward* aNode) { + aNode->SetActualTraversalRank(visitCount); + visitCount++; + return aNode->GetType() == SearchNodeType::Needle; + }); + + for (size_t i = 0; i < nodeList.size(); i++) { + ASSERT_EQ(nodeList[i]->GetExpectedTraversalRank(), + nodeList[i]->GetActualTraversalRank()) + << "Node at index " << i << " was hit out of order."; + } + + ASSERT_EQ(foundNode, needleNode) << "Search did not return expected node."; + ASSERT_EQ(foundNode->GetType(), SearchNodeType::Needle) + << "Returned node does not match expected value (something odd " + "happened)."; +} + +TEST(TreeTraversal, BreadthFirstSearchValueExistsReverse) +{ + int visitCount = 0; + size_t expectedNeedleTraversalRank = 7; + RefPtr<SearchTestNodeReverse> needleNode; + std::vector<RefPtr<SearchTestNodeReverse>> nodeList; + nodeList.reserve(10); + for (size_t i = 0; i < 10; i++) { + if (i == expectedNeedleTraversalRank) { + needleNode = new SearchTestNodeReverse(SearchNodeType::Needle, i); + nodeList.push_back(needleNode); + } else if (i < expectedNeedleTraversalRank) { + nodeList.push_back(new SearchTestNodeReverse(SearchNodeType::Hay, i)); + } else { + nodeList.push_back(new SearchTestNodeReverse(SearchNodeType::Hay)); + } + } + + RefPtr<SearchTestNodeReverse> root = nodeList[0]; + nodeList[0]->AddChild(nodeList[2]); + nodeList[0]->AddChild(nodeList[1]); + nodeList[1]->AddChild(nodeList[4]); + nodeList[1]->AddChild(nodeList[3]); + nodeList[2]->AddChild(nodeList[6]); + nodeList[2]->AddChild(nodeList[5]); + nodeList[6]->AddChild(nodeList[7]); + nodeList[7]->AddChild(nodeList[9]); + nodeList[7]->AddChild(nodeList[8]); + + RefPtr<SearchTestNodeReverse> foundNode = + BreadthFirstSearch<layers::ReverseIterator>( + root.get(), [&visitCount](SearchTestNodeReverse* aNode) { + aNode->SetActualTraversalRank(visitCount); + visitCount++; + return aNode->GetType() == SearchNodeType::Needle; + }); + + for (size_t i = 0; i < nodeList.size(); i++) { + ASSERT_EQ(nodeList[i]->GetExpectedTraversalRank(), + nodeList[i]->GetActualTraversalRank()) + << "Node at index " << i << " was hit out of order."; + } + + ASSERT_EQ(foundNode, needleNode) << "Search did not return expected node."; + ASSERT_EQ(foundNode->GetType(), SearchNodeType::Needle) + << "Returned node does not match expected value (something odd " + "happened)."; +} + +TEST(TreeTraversal, BreadthFirstSearchValueDoesNotExist) +{ + int visitCount = 0; + std::vector<RefPtr<SearchTestNodeForward>> nodeList; + nodeList.reserve(10); + for (int i = 0; i < 10; i++) { + nodeList.push_back(new SearchTestNodeForward(SearchNodeType::Hay, i)); + } + + RefPtr<SearchTestNodeForward> root = nodeList[0]; + nodeList[0]->AddChild(nodeList[1]); + nodeList[0]->AddChild(nodeList[2]); + nodeList[1]->AddChild(nodeList[3]); + nodeList[1]->AddChild(nodeList[4]); + nodeList[2]->AddChild(nodeList[5]); + nodeList[2]->AddChild(nodeList[6]); + nodeList[6]->AddChild(nodeList[7]); + nodeList[7]->AddChild(nodeList[8]); + nodeList[7]->AddChild(nodeList[9]); + + RefPtr<SearchTestNodeForward> foundNode = + BreadthFirstSearch<layers::ForwardIterator>( + root.get(), [&visitCount](SearchTestNodeForward* aNode) { + aNode->SetActualTraversalRank(visitCount); + visitCount++; + return aNode->GetType() == SearchNodeType::Needle; + }); + + for (size_t i = 0; i < nodeList.size(); i++) { + ASSERT_EQ(nodeList[i]->GetExpectedTraversalRank(), + nodeList[i]->GetActualTraversalRank()) + << "Node at index " << i << " was hit out of order."; + } + + ASSERT_EQ(foundNode.get(), nullptr) + << "Search found something that should not exist."; +} + +TEST(TreeTraversal, BreadthFirstSearchValueDoesNotExistReverse) +{ + int visitCount = 0; + std::vector<RefPtr<SearchTestNodeReverse>> nodeList; + nodeList.reserve(10); + for (int i = 0; i < 10; i++) { + nodeList.push_back(new SearchTestNodeReverse(SearchNodeType::Hay, i)); + } + + RefPtr<SearchTestNodeReverse> root = nodeList[0]; + nodeList[0]->AddChild(nodeList[2]); + nodeList[0]->AddChild(nodeList[1]); + nodeList[1]->AddChild(nodeList[4]); + nodeList[1]->AddChild(nodeList[3]); + nodeList[2]->AddChild(nodeList[6]); + nodeList[2]->AddChild(nodeList[5]); + nodeList[6]->AddChild(nodeList[7]); + nodeList[7]->AddChild(nodeList[9]); + nodeList[7]->AddChild(nodeList[8]); + + RefPtr<SearchTestNodeReverse> foundNode = + BreadthFirstSearch<layers::ReverseIterator>( + root.get(), [&visitCount](SearchTestNodeReverse* aNode) { + aNode->SetActualTraversalRank(visitCount); + visitCount++; + return aNode->GetType() == SearchNodeType::Needle; + }); + + for (size_t i = 0; i < nodeList.size(); i++) { + ASSERT_EQ(nodeList[i]->GetExpectedTraversalRank(), + nodeList[i]->GetActualTraversalRank()) + << "Node at index " << i << " was hit out of order."; + } + + ASSERT_EQ(foundNode.get(), nullptr) + << "Search found something that should not exist."; +} + +TEST(TreeTraversal, ForEachNodeNullStillRuns) +{ + RefPtr<ForEachTestNodeReverse> nullNode; + ForEachNode<layers::ReverseIterator>( + nullNode.get(), + [](ForEachTestNodeReverse* aNode) { return TraversalFlag::Continue; }); +} + +TEST(TreeTraversal, ForEachNodeAllEligible) +{ + std::vector<RefPtr<ForEachTestNodeForward>> nodeList; + int visitCount = 0; + nodeList.reserve(10); + for (int i = 0; i < 10; i++) { + nodeList.push_back( + new ForEachTestNodeForward(ForEachNodeType::Continue, i)); + } + + RefPtr<ForEachTestNodeForward> root = nodeList[0]; + nodeList[0]->AddChild(nodeList[1]); + nodeList[0]->AddChild(nodeList[4]); + nodeList[1]->AddChild(nodeList[2]); + nodeList[1]->AddChild(nodeList[3]); + nodeList[4]->AddChild(nodeList[5]); + nodeList[4]->AddChild(nodeList[6]); + nodeList[6]->AddChild(nodeList[7]); + nodeList[7]->AddChild(nodeList[8]); + nodeList[7]->AddChild(nodeList[9]); + + ForEachNode<layers::ForwardIterator>( + root.get(), [&visitCount](ForEachTestNodeForward* aNode) { + aNode->SetActualTraversalRank(visitCount); + visitCount++; + return aNode->GetType() == ForEachNodeType::Continue + ? TraversalFlag::Continue + : TraversalFlag::Skip; + }); + + for (size_t i = 0; i < nodeList.size(); i++) { + ASSERT_EQ(nodeList[i]->GetExpectedTraversalRank(), + nodeList[i]->GetActualTraversalRank()) + << "Node at index " << i << " was hit out of order."; + } +} + +TEST(TreeTraversal, ForEachNodeAllEligibleReverse) +{ + std::vector<RefPtr<ForEachTestNodeReverse>> nodeList; + int visitCount = 0; + nodeList.reserve(10); + for (int i = 0; i < 10; i++) { + nodeList.push_back( + new ForEachTestNodeReverse(ForEachNodeType::Continue, i)); + } + + RefPtr<ForEachTestNodeReverse> root = nodeList[0]; + nodeList[0]->AddChild(nodeList[4]); + nodeList[0]->AddChild(nodeList[1]); + nodeList[1]->AddChild(nodeList[3]); + nodeList[1]->AddChild(nodeList[2]); + nodeList[4]->AddChild(nodeList[6]); + nodeList[4]->AddChild(nodeList[5]); + nodeList[6]->AddChild(nodeList[7]); + nodeList[7]->AddChild(nodeList[9]); + nodeList[7]->AddChild(nodeList[8]); + + ForEachNode<layers::ReverseIterator>( + root.get(), [&visitCount](ForEachTestNodeReverse* aNode) { + aNode->SetActualTraversalRank(visitCount); + visitCount++; + return aNode->GetType() == ForEachNodeType::Continue + ? TraversalFlag::Continue + : TraversalFlag::Skip; + }); + + for (size_t i = 0; i < nodeList.size(); i++) { + ASSERT_EQ(nodeList[i]->GetExpectedTraversalRank(), + nodeList[i]->GetActualTraversalRank()) + << "Node at index " << i << " was hit out of order."; + } +} + +TEST(TreeTraversal, ForEachNodeSomeIneligibleNodes) +{ + std::vector<RefPtr<ForEachTestNodeForward>> expectedVisitedNodeList; + std::vector<RefPtr<ForEachTestNodeForward>> expectedSkippedNodeList; + int visitCount = 0; + + expectedVisitedNodeList.push_back( + new ForEachTestNodeForward(ForEachNodeType::Continue, 0)); + expectedVisitedNodeList.push_back( + new ForEachTestNodeForward(ForEachNodeType::Skip, 1)); + expectedVisitedNodeList.push_back( + new ForEachTestNodeForward(ForEachNodeType::Continue, 2)); + expectedVisitedNodeList.push_back( + new ForEachTestNodeForward(ForEachNodeType::Skip, 3)); + + expectedSkippedNodeList.push_back( + new ForEachTestNodeForward(ForEachNodeType::Continue)); + expectedSkippedNodeList.push_back( + new ForEachTestNodeForward(ForEachNodeType::Continue)); + expectedSkippedNodeList.push_back( + new ForEachTestNodeForward(ForEachNodeType::Skip)); + expectedSkippedNodeList.push_back( + new ForEachTestNodeForward(ForEachNodeType::Skip)); + + RefPtr<ForEachTestNodeForward> root = expectedVisitedNodeList[0]; + expectedVisitedNodeList[0]->AddChild(expectedVisitedNodeList[1]); + expectedVisitedNodeList[0]->AddChild(expectedVisitedNodeList[2]); + expectedVisitedNodeList[1]->AddChild(expectedSkippedNodeList[0]); + expectedVisitedNodeList[1]->AddChild(expectedSkippedNodeList[1]); + expectedVisitedNodeList[2]->AddChild(expectedVisitedNodeList[3]); + expectedVisitedNodeList[3]->AddChild(expectedSkippedNodeList[2]); + expectedVisitedNodeList[3]->AddChild(expectedSkippedNodeList[3]); + + ForEachNode<layers::ForwardIterator>( + root.get(), [&visitCount](ForEachTestNodeForward* aNode) { + aNode->SetActualTraversalRank(visitCount); + visitCount++; + return aNode->GetType() == ForEachNodeType::Continue + ? TraversalFlag::Continue + : TraversalFlag::Skip; + }); + + for (size_t i = 0; i < expectedVisitedNodeList.size(); i++) { + ASSERT_EQ(expectedVisitedNodeList[i]->GetExpectedTraversalRank(), + expectedVisitedNodeList[i]->GetActualTraversalRank()) + << "Node at index " << i << " was hit out of order."; + } + + for (size_t i = 0; i < expectedSkippedNodeList.size(); i++) { + ASSERT_EQ(expectedSkippedNodeList[i]->GetExpectedTraversalRank(), + expectedSkippedNodeList[i]->GetActualTraversalRank()) + << "Node at index " << i << "was not expected to be hit."; + } +} + +TEST(TreeTraversal, ForEachNodeSomeIneligibleNodesReverse) +{ + std::vector<RefPtr<ForEachTestNodeReverse>> expectedVisitedNodeList; + std::vector<RefPtr<ForEachTestNodeReverse>> expectedSkippedNodeList; + int visitCount = 0; + + expectedVisitedNodeList.push_back( + new ForEachTestNodeReverse(ForEachNodeType::Continue, 0)); + expectedVisitedNodeList.push_back( + new ForEachTestNodeReverse(ForEachNodeType::Skip, 1)); + expectedVisitedNodeList.push_back( + new ForEachTestNodeReverse(ForEachNodeType::Continue, 2)); + expectedVisitedNodeList.push_back( + new ForEachTestNodeReverse(ForEachNodeType::Skip, 3)); + + expectedSkippedNodeList.push_back( + new ForEachTestNodeReverse(ForEachNodeType::Continue)); + expectedSkippedNodeList.push_back( + new ForEachTestNodeReverse(ForEachNodeType::Continue)); + expectedSkippedNodeList.push_back( + new ForEachTestNodeReverse(ForEachNodeType::Skip)); + expectedSkippedNodeList.push_back( + new ForEachTestNodeReverse(ForEachNodeType::Skip)); + + RefPtr<ForEachTestNodeReverse> root = expectedVisitedNodeList[0]; + expectedVisitedNodeList[0]->AddChild(expectedVisitedNodeList[2]); + expectedVisitedNodeList[0]->AddChild(expectedVisitedNodeList[1]); + expectedVisitedNodeList[1]->AddChild(expectedSkippedNodeList[1]); + expectedVisitedNodeList[1]->AddChild(expectedSkippedNodeList[0]); + expectedVisitedNodeList[2]->AddChild(expectedVisitedNodeList[3]); + expectedVisitedNodeList[3]->AddChild(expectedSkippedNodeList[3]); + expectedVisitedNodeList[3]->AddChild(expectedSkippedNodeList[2]); + + ForEachNode<layers::ReverseIterator>( + root.get(), [&visitCount](ForEachTestNodeReverse* aNode) { + aNode->SetActualTraversalRank(visitCount); + visitCount++; + return aNode->GetType() == ForEachNodeType::Continue + ? TraversalFlag::Continue + : TraversalFlag::Skip; + }); + + for (size_t i = 0; i < expectedVisitedNodeList.size(); i++) { + ASSERT_EQ(expectedVisitedNodeList[i]->GetExpectedTraversalRank(), + expectedVisitedNodeList[i]->GetActualTraversalRank()) + << "Node at index " << i << " was hit out of order."; + } + + for (size_t i = 0; i < expectedSkippedNodeList.size(); i++) { + ASSERT_EQ(expectedSkippedNodeList[i]->GetExpectedTraversalRank(), + expectedSkippedNodeList[i]->GetActualTraversalRank()) + << "Node at index " << i << "was not expected to be hit."; + } +} + +TEST(TreeTraversal, ForEachNodeIneligibleRoot) +{ + int visitCount = 0; + + RefPtr<ForEachTestNodeReverse> root = + new ForEachTestNodeReverse(ForEachNodeType::Skip, 0); + RefPtr<ForEachTestNodeReverse> childNode1 = + new ForEachTestNodeReverse(ForEachNodeType::Continue); + RefPtr<ForEachTestNodeReverse> chlidNode2 = + new ForEachTestNodeReverse(ForEachNodeType::Skip); + + ForEachNode<layers::ReverseIterator>( + root.get(), [&visitCount](ForEachTestNodeReverse* aNode) { + aNode->SetActualTraversalRank(visitCount); + visitCount++; + return aNode->GetType() == ForEachNodeType::Continue + ? TraversalFlag::Continue + : TraversalFlag::Skip; + }); + + ASSERT_EQ(root->GetExpectedTraversalRank(), root->GetActualTraversalRank()) + << "Root was hit out of order."; + ASSERT_EQ(childNode1->GetExpectedTraversalRank(), + childNode1->GetActualTraversalRank()) + << "Eligible child was still hit."; + ASSERT_EQ(chlidNode2->GetExpectedTraversalRank(), + chlidNode2->GetActualTraversalRank()) + << "Ineligible child was still hit."; +} + +TEST(TreeTraversal, ForEachNodeLeavesIneligible) +{ + std::vector<RefPtr<ForEachTestNodeForward>> nodeList; + nodeList.reserve(10); + int visitCount = 0; + for (int i = 0; i < 10; i++) { + if (i == 1 || i == 9) { + nodeList.push_back(new ForEachTestNodeForward(ForEachNodeType::Skip, i)); + } else { + nodeList.push_back( + new ForEachTestNodeForward(ForEachNodeType::Continue, i)); + } + } + + RefPtr<ForEachTestNodeForward> root = nodeList[0]; + nodeList[0]->AddChild(nodeList[1]); + nodeList[0]->AddChild(nodeList[2]); + nodeList[2]->AddChild(nodeList[3]); + nodeList[2]->AddChild(nodeList[4]); + nodeList[4]->AddChild(nodeList[5]); + nodeList[4]->AddChild(nodeList[6]); + nodeList[6]->AddChild(nodeList[7]); + nodeList[7]->AddChild(nodeList[8]); + nodeList[7]->AddChild(nodeList[9]); + + ForEachNode<layers::ForwardIterator>( + root.get(), [&visitCount](ForEachTestNodeForward* aNode) { + aNode->SetActualTraversalRank(visitCount); + visitCount++; + return aNode->GetType() == ForEachNodeType::Continue + ? TraversalFlag::Continue + : TraversalFlag::Skip; + }); + + for (size_t i = 0; i < nodeList.size(); i++) { + ASSERT_EQ(nodeList[i]->GetExpectedTraversalRank(), + nodeList[i]->GetActualTraversalRank()) + << "Node at index " << i << " was hit out of order."; + } +} + +TEST(TreeTraversal, ForEachNodeLeavesIneligibleReverse) +{ + std::vector<RefPtr<ForEachTestNodeReverse>> nodeList; + nodeList.reserve(10); + int visitCount = 0; + for (int i = 0; i < 10; i++) { + if (i == 1 || i == 9) { + nodeList.push_back(new ForEachTestNodeReverse(ForEachNodeType::Skip, i)); + } else { + nodeList.push_back( + new ForEachTestNodeReverse(ForEachNodeType::Continue, i)); + } + } + + RefPtr<ForEachTestNodeReverse> root = nodeList[0]; + nodeList[0]->AddChild(nodeList[2]); + nodeList[0]->AddChild(nodeList[1]); + nodeList[2]->AddChild(nodeList[4]); + nodeList[2]->AddChild(nodeList[3]); + nodeList[4]->AddChild(nodeList[6]); + nodeList[4]->AddChild(nodeList[5]); + nodeList[6]->AddChild(nodeList[7]); + nodeList[7]->AddChild(nodeList[9]); + nodeList[7]->AddChild(nodeList[8]); + + ForEachNode<layers::ReverseIterator>( + root.get(), [&visitCount](ForEachTestNodeReverse* aNode) { + aNode->SetActualTraversalRank(visitCount); + visitCount++; + return aNode->GetType() == ForEachNodeType::Continue + ? TraversalFlag::Continue + : TraversalFlag::Skip; + }); + + for (size_t i = 0; i < nodeList.size(); i++) { + ASSERT_EQ(nodeList[i]->GetExpectedTraversalRank(), + nodeList[i]->GetActualTraversalRank()) + << "Node at index " << i << " was hit out of order."; + } +} + +TEST(TreeTraversal, ForEachNodeLambdaReturnsVoid) +{ + std::vector<RefPtr<ForEachTestNodeReverse>> nodeList; + nodeList.reserve(10); + int visitCount = 0; + for (int i = 0; i < 10; i++) { + nodeList.push_back( + new ForEachTestNodeReverse(ForEachNodeType::Continue, i)); + } + + RefPtr<ForEachTestNodeReverse> root = nodeList[0]; + nodeList[0]->AddChild(nodeList[4]); + nodeList[0]->AddChild(nodeList[1]); + nodeList[1]->AddChild(nodeList[3]); + nodeList[1]->AddChild(nodeList[2]); + nodeList[4]->AddChild(nodeList[6]); + nodeList[4]->AddChild(nodeList[5]); + nodeList[6]->AddChild(nodeList[7]); + nodeList[7]->AddChild(nodeList[9]); + nodeList[7]->AddChild(nodeList[8]); + + ForEachNode<layers::ReverseIterator>( + root.get(), [&visitCount](ForEachTestNodeReverse* aNode) { + aNode->SetActualTraversalRank(visitCount); + visitCount++; + }); + + for (size_t i = 0; i < nodeList.size(); i++) { + ASSERT_EQ(nodeList[i]->GetExpectedTraversalRank(), + nodeList[i]->GetActualTraversalRank()) + << "Node at index " << i << " was hit out of order."; + } +} + +struct AssignSearchNodeTypesWithLastLeafAsNeedle { + RefPtr<SearchTestNodeForward>& node; + void operator()(SearchTestNodeForward* aNode) { + aNode->SetType(SearchNodeType::Hay); + if (aNode->IsLeaf()) { + node = aNode; + } + } +}; + +struct AssignSearchNodeTypesAllHay { + void operator()(SearchTestNode* aNode) { + aNode->SetType(SearchNodeType::Hay); + } +}; + +struct AssignSearchNodeTypesWithFirstLeafAsNeedle { + RefPtr<SearchTestNodeReverse>& needleNode; + void operator()(SearchTestNodeReverse* aNode) { + if (!needleNode && aNode->IsLeaf()) { + needleNode = aNode; + } + aNode->SetType(SearchNodeType::Hay); + } +}; + +struct AssignSearchNodeValuesAllFalseValuesReverse { + int falseValue; + RefPtr<SearchTestNodeReverse>& needleNode; + void operator()(SearchTestNodeReverse* aNode) { + aNode->SetValue(falseValue); + if (!needleNode && aNode->IsLeaf()) { + needleNode = aNode; + } + } +}; + +struct AssignSearchNodeValuesAllFalseValuesForward { + int falseValue; + RefPtr<SearchTestNodeForward>& needleNode; + void operator()(SearchTestNodeForward* aNode) { + aNode->SetValue(falseValue); + needleNode = aNode; + } +}; + +struct AllocateUnitRegionsToLeavesOnly { + int& xWrap; + int& squareCount; + void operator()(ForEachTestNode* aNode) { + if (aNode->IsLeaf()) { + int x = squareCount % xWrap; + int y = squareCount / xWrap; + aNode->SetRegion(nsRegion(nsRect(x, y, 1, 1))); + squareCount++; + } + } +}; + +template <typename Node> +static RefPtr<Node> DepthFirstSearchForwardRecursive(RefPtr<Node> aNode) { + if (aNode->GetType() == SearchNodeType::Needle) { + return aNode; + } + for (RefPtr<Node> node = aNode->GetFirstChild(); node != nullptr; + node = node->GetNextSibling()) { + if (RefPtr<Node> foundNode = DepthFirstSearchForwardRecursive(node)) { + return foundNode; + } + } + return nullptr; +} + +template <typename Node> +static RefPtr<Node> DepthFirstSearchCaptureVariablesForwardRecursive( + RefPtr<Node> aNode, int a, int b, int c, int d, int e, int f, int g, int h, + int i, int j, int k, int l, int m, int& n, int& o, int& p, int& q, int& r, + int& s, int& t, int& u, int& v, int& w, int& x, int& y, int& z) { + if (aNode->GetValue() == a + b + c + d + e + f + g + h + i + j + k + l + m + + n + o + p + q + r + s + t + u + v + w + x + y + + z) { + return aNode; + } + for (RefPtr<Node> node = aNode->GetFirstChild(); node != nullptr; + node = node->GetNextSibling()) { + if (RefPtr<Node> foundNode = + DepthFirstSearchCaptureVariablesForwardRecursive( + node, a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, + t, u, v, w, x, y, z)) { + return foundNode; + } + } + return nullptr; +} + +template <typename Node> +static RefPtr<Node> DepthFirstSearchPostOrderForwardRecursive( + RefPtr<Node> aNode) { + for (RefPtr<Node> node = aNode->GetFirstChild(); node != nullptr; + node = node->GetNextSibling()) { + if (RefPtr<Node> foundNode = + DepthFirstSearchPostOrderForwardRecursive(node)) { + return foundNode; + } + } + if (aNode->GetType() == SearchNodeType::Needle) { + return aNode; + } + return nullptr; +} + +template <typename Node> +static RefPtr<Node> BreadthFirstSearchForwardQueue(RefPtr<Node> aNode) { + std::queue<RefPtr<Node>> nodes; + nodes.push(aNode); + while (!nodes.empty()) { + RefPtr<Node> node = nodes.front(); + nodes.pop(); + if (node->GetType() == SearchNodeType::Needle) { + return node; + } + for (RefPtr<Node> childNode = node->GetFirstChild(); childNode != nullptr; + childNode = childNode->GetNextSibling()) { + nodes.push(childNode); + } + } + return nullptr; +} + +template <typename Node> +static RefPtr<Node> DepthFirstSearchReverseRecursive(RefPtr<Node> aNode) { + if (aNode->GetType() == SearchNodeType::Needle) { + return aNode; + } + for (RefPtr<Node> node = aNode->GetLastChild(); node != nullptr; + node = node->GetPrevSibling()) { + if (RefPtr<Node> foundNode = DepthFirstSearchReverseRecursive(node)) { + return foundNode; + } + } + return nullptr; +} + +template <typename Node> +static RefPtr<Node> DepthFirstSearchCaptureVariablesReverseRecursive( + RefPtr<Node> aNode, int a, int b, int c, int d, int e, int f, int g, int h, + int i, int j, int k, int l, int m, int& n, int& o, int& p, int& q, int& r, + int& s, int& t, int& u, int& v, int& w, int& x, int& y, int& z) { + if (aNode->GetValue() == a + b + c + d + e + f + g + h + i + j + k + l + m + + n + o + p + q + r + s + t + u + v + w + x + y + + z) { + return aNode; + } + for (RefPtr<Node> node = aNode->GetLastChild(); node != nullptr; + node = node->GetPrevSibling()) { + if (RefPtr<Node> foundNode = + DepthFirstSearchCaptureVariablesReverseRecursive( + node, a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, + t, u, v, w, x, y, z)) { + return foundNode; + } + } + return nullptr; +} + +template <typename Node> +static RefPtr<Node> DepthFirstSearchPostOrderReverseRecursive( + RefPtr<Node> aNode) { + for (RefPtr<Node> node = aNode->GetLastChild(); node != nullptr; + node = node->GetPrevSibling()) { + if (RefPtr<Node> foundNode = + DepthFirstSearchPostOrderReverseRecursive(node)) { + return foundNode; + } + } + if (aNode->GetType() == SearchNodeType::Needle) { + return aNode; + } + return nullptr; +} + +template <typename Node> +static RefPtr<Node> BreadthFirstSearchReverseQueue(RefPtr<Node> aNode) { + std::queue<RefPtr<Node>> nodes; + nodes.push(aNode); + while (!nodes.empty()) { + RefPtr<Node> node = nodes.front(); + nodes.pop(); + if (node->GetType() == SearchNodeType::Needle) { + return node; + } + for (RefPtr<Node> childNode = node->GetLastChild(); childNode != nullptr; + childNode = childNode->GetPrevSibling()) { + nodes.push(childNode); + } + } + return nullptr; +} diff --git a/gfx/tests/gtest/TestVsync.cpp b/gfx/tests/gtest/TestVsync.cpp new file mode 100644 index 0000000000..7249e7d26c --- /dev/null +++ b/gfx/tests/gtest/TestVsync.cpp @@ -0,0 +1,173 @@ +/* vim:set ts=2 sw=2 sts=2 et: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "gfxPlatform.h" + +#include "MainThreadUtils.h" +#include "nsIThread.h" +#include "mozilla/RefPtr.h" +#include "SoftwareVsyncSource.h" +#include "VsyncSource.h" +#include "mozilla/gtest/MozAssertions.h" +#include "mozilla/Monitor.h" +#include "mozilla/TimeStamp.h" +#include "mozilla/VsyncDispatcher.h" + +using namespace mozilla; +using namespace mozilla::gfx; +using namespace mozilla::layers; +using ::testing::_; + +// Timeout for vsync events to occur in milliseconds +// Windows 8.1 has intermittents at 50 ms. Raise limit to 5 vsync intervals. +const int kVsyncTimeoutMS = 80; + +class TestVsyncObserver : public VsyncObserver { + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(TestVsyncObserver, override) + + public: + TestVsyncObserver() + : mDidGetVsyncNotification(false), mVsyncMonitor("VsyncMonitor") {} + + virtual void NotifyVsync(const VsyncEvent& aVsync) override { + MonitorAutoLock lock(mVsyncMonitor); + mDidGetVsyncNotification = true; + mVsyncMonitor.Notify(); + } + + void WaitForVsyncNotification() { + MOZ_ASSERT(NS_IsMainThread()); + if (DidGetVsyncNotification()) { + return; + } + + { // scope lock + MonitorAutoLock lock(mVsyncMonitor); + lock.Wait(TimeDuration::FromMilliseconds(kVsyncTimeoutMS)); + } + } + + bool DidGetVsyncNotification() { + MonitorAutoLock lock(mVsyncMonitor); + return mDidGetVsyncNotification; + } + + void ResetVsyncNotification() { + MonitorAutoLock lock(mVsyncMonitor); + mDidGetVsyncNotification = false; + } + + private: + ~TestVsyncObserver() = default; + + bool mDidGetVsyncNotification; + + private: + Monitor mVsyncMonitor MOZ_UNANNOTATED; +}; + +class VsyncTester : public ::testing::Test { + protected: + explicit VsyncTester() { + gfxPlatform::GetPlatform(); + mVsyncDispatcher = gfxPlatform::GetPlatform()->GetGlobalVsyncDispatcher(); + mVsyncSource = mVsyncDispatcher->GetCurrentVsyncSource(); + MOZ_RELEASE_ASSERT(mVsyncSource, "GFX: Vsync source not found."); + } + + virtual ~VsyncTester() { mVsyncSource = nullptr; } + + RefPtr<VsyncDispatcher> mVsyncDispatcher; + RefPtr<VsyncSource> mVsyncSource; +}; + +static void FlushMainThreadLoop() { + // Some tasks are pushed onto the main thread when adding vsync observers + // This function will ensure all tasks are executed on the main thread + // before returning. + nsCOMPtr<nsIThread> mainThread; + nsresult rv = NS_GetMainThread(getter_AddRefs(mainThread)); + ASSERT_NS_SUCCEEDED(rv); + + rv = NS_OK; + bool processed = true; + while (processed && NS_SUCCEEDED(rv)) { + rv = mainThread->ProcessNextEvent(false, &processed); + } +} + +// Tests that we can enable/disable vsync notifications +TEST_F(VsyncTester, EnableVsync) { + mVsyncSource->DisableVsync(); + ASSERT_FALSE(mVsyncSource->IsVsyncEnabled()); + + mVsyncSource->EnableVsync(); + ASSERT_TRUE(mVsyncSource->IsVsyncEnabled()); + + mVsyncSource->DisableVsync(); + ASSERT_FALSE(mVsyncSource->IsVsyncEnabled()); +} + +// Test that if we have vsync enabled, the source should get vsync +// notifications +TEST_F(VsyncTester, CompositorGetVsyncNotifications) { + mVsyncSource->DisableVsync(); + ASSERT_FALSE(mVsyncSource->IsVsyncEnabled()); + + RefPtr<CompositorVsyncDispatcher> vsyncDispatcher = + new CompositorVsyncDispatcher(mVsyncDispatcher); + RefPtr<TestVsyncObserver> testVsyncObserver = new TestVsyncObserver(); + + vsyncDispatcher->SetCompositorVsyncObserver(testVsyncObserver); + FlushMainThreadLoop(); + ASSERT_TRUE(mVsyncSource->IsVsyncEnabled()); + + testVsyncObserver->WaitForVsyncNotification(); + ASSERT_TRUE(testVsyncObserver->DidGetVsyncNotification()); + + vsyncDispatcher->SetCompositorVsyncObserver(nullptr); + FlushMainThreadLoop(); + + vsyncDispatcher = nullptr; + testVsyncObserver = nullptr; + ASSERT_FALSE(mVsyncSource->IsVsyncEnabled()); +} + +// Test that child refresh vsync observers get vsync notifications +TEST_F(VsyncTester, ChildRefreshDriverGetVsyncNotifications) { + mVsyncSource->DisableVsync(); + ASSERT_FALSE(mVsyncSource->IsVsyncEnabled()); + + ASSERT_TRUE(mVsyncDispatcher != nullptr); + + RefPtr<TestVsyncObserver> testVsyncObserver = new TestVsyncObserver(); + mVsyncDispatcher->AddVsyncObserver(testVsyncObserver); + ASSERT_TRUE(mVsyncSource->IsVsyncEnabled()); + + testVsyncObserver->WaitForVsyncNotification(); + ASSERT_TRUE(testVsyncObserver->DidGetVsyncNotification()); + + mVsyncDispatcher->RemoveVsyncObserver(testVsyncObserver); + testVsyncObserver->ResetVsyncNotification(); + testVsyncObserver->WaitForVsyncNotification(); + ASSERT_FALSE(testVsyncObserver->DidGetVsyncNotification()); + + testVsyncObserver = nullptr; + + mVsyncSource->DisableVsync(); + ASSERT_FALSE(mVsyncSource->IsVsyncEnabled()); +} + +// Test that we can read the vsync rate +TEST_F(VsyncTester, VsyncSourceHasVsyncRate) { + TimeDuration vsyncRate = mVsyncSource->GetVsyncRate(); + ASSERT_NE(vsyncRate, TimeDuration::Forever()); + ASSERT_GT(vsyncRate.ToMilliseconds(), 0); + + mVsyncSource->DisableVsync(); + ASSERT_FALSE(mVsyncSource->IsVsyncEnabled()); +} diff --git a/gfx/tests/gtest/TextureHelper.h b/gfx/tests/gtest/TextureHelper.h new file mode 100644 index 0000000000..9fa1a07096 --- /dev/null +++ b/gfx/tests/gtest/TextureHelper.h @@ -0,0 +1,134 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <vector> + +#include "Types.h" +#include "gfxImageSurface.h" +#include "gfxPlatform.h" +#include "mozilla/RefPtr.h" +#include "mozilla/gfx/Point.h" +#include "mozilla/layers/BufferTexture.h" +#include "mozilla/layers/LayersTypes.h" +#include "mozilla/layers/TextureClient.h" +#include "mozilla/layers/TextureHost.h" +#ifdef XP_WIN +# include "IMFYCbCrImage.h" +# include "mozilla/gfx/DeviceManagerDx.h" +# include "mozilla/layers/D3D11YCbCrImage.h" +# include "mozilla/layers/TextureD3D11.h" +#endif + +namespace mozilla { +namespace layers { + +using gfx::BackendType; +using gfx::IntSize; +using gfx::SurfaceFormat; + +/** + * Create a YCbCrTextureClient according to the given backend. + */ +static already_AddRefed<TextureClient> CreateYCbCrTextureClientWithBackend( + LayersBackend aLayersBackend) { + TextureData* data = nullptr; + IntSize size = IntSize(200, 150); + IntSize ySize = IntSize(400, 300); + + RefPtr<gfxImageSurface> ySurface = + new gfxImageSurface(ySize, SurfaceFormat::A8); + RefPtr<gfxImageSurface> cbSurface = + new gfxImageSurface(size, SurfaceFormat::A8); + RefPtr<gfxImageSurface> crSurface = + new gfxImageSurface(size, SurfaceFormat::A8); + + PlanarYCbCrData clientData; + clientData.mYChannel = ySurface->Data(); + clientData.mCbChannel = cbSurface->Data(); + clientData.mCrChannel = crSurface->Data(); + clientData.mPictureRect = IntRect(IntPoint(0, 0), ySurface->GetSize()); + clientData.mYStride = ySurface->Stride(); + clientData.mCbCrStride = cbSurface->Stride(); + clientData.mStereoMode = StereoMode::MONO; + clientData.mChromaSubsampling = ChromaSubsampling::HALF_WIDTH_AND_HEIGHT; + clientData.mYSkip = 0; + clientData.mCbSkip = 0; + clientData.mCrSkip = 0; + clientData.mCrSkip = 0; + + // Create YCbCrTexture for basic backend. + if (aLayersBackend == LayersBackend::LAYERS_BASIC) { + return TextureClient::CreateForYCbCr( + nullptr, clientData.mPictureRect, clientData.YDataSize(), + clientData.mYStride, clientData.CbCrDataSize(), clientData.mCbCrStride, + StereoMode::MONO, gfx::ColorDepth::COLOR_8, gfx::YUVColorSpace::BT601, + gfx::ColorRange::LIMITED, clientData.mChromaSubsampling, + TextureFlags::DEALLOCATE_CLIENT); + } + + if (data) { + return MakeAndAddRef<TextureClient>(data, TextureFlags::DEALLOCATE_CLIENT, + nullptr); + } + + return nullptr; +} + +/** + * Create a TextureClient according to the given backend. + */ +static already_AddRefed<TextureClient> CreateTextureClientWithBackend( + LayersBackend aLayersBackend) { + TextureData* data = nullptr; + SurfaceFormat format = gfxPlatform::GetPlatform()->Optimal2DFormatForContent( + gfxContentType::COLOR_ALPHA); + BackendType moz2DBackend = + gfxPlatform::GetPlatform()->GetContentBackendFor(aLayersBackend); + TextureAllocationFlags allocFlags = TextureAllocationFlags::ALLOC_DEFAULT; + IntSize size = IntSize(400, 300); + TextureFlags textureFlags = TextureFlags::DEALLOCATE_CLIENT; + + if (!gfx::Factory::AllowedSurfaceSize(size)) { + return nullptr; + } + + if (!data && aLayersBackend == LayersBackend::LAYERS_BASIC) { + // Create BufferTextureData. + data = BufferTextureData::Create(size, format, moz2DBackend, aLayersBackend, + textureFlags, allocFlags, nullptr); + } + + if (data) { + return MakeAndAddRef<TextureClient>(data, textureFlags, nullptr); + } + + return nullptr; +} + +/** + * Create a TextureHost according to the given TextureClient. + */ +already_AddRefed<TextureHost> CreateTextureHostWithBackend( + TextureClient* aClient, ISurfaceAllocator* aDeallocator, + LayersBackend& aLayersBackend) { + if (!aClient) { + return nullptr; + } + + // client serialization + SurfaceDescriptor descriptor; + ReadLockDescriptor readLock = null_t(); + RefPtr<TextureHost> textureHost; + + aClient->ToSurfaceDescriptor(descriptor); + + wr::MaybeExternalImageId id = Nothing(); + return TextureHost::Create(descriptor, readLock, aDeallocator, aLayersBackend, + aClient->GetFlags(), id); +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/tests/gtest/gfxSurfaceRefCountTest.cpp b/gfx/tests/gtest/gfxSurfaceRefCountTest.cpp new file mode 100644 index 0000000000..77ab5aeb91 --- /dev/null +++ b/gfx/tests/gtest/gfxSurfaceRefCountTest.cpp @@ -0,0 +1,155 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <stdio.h> + +#include "gtest/gtest.h" + +#include "gfxASurface.h" +#include "gfxImageSurface.h" + +#include "nsISupportsUtils.h" // for NS_ADDREF + +#include "cairo.h" + +static int GetASurfaceRefCount(gfxASurface* s) { + NS_ADDREF(s); + return s->Release(); +} + +static int CheckInt(int value, int expected) { + if (value != expected) { + fprintf(stderr, "Expected %d got %d\n", expected, value); + return 1; + } + + return 0; +} + +static int CheckPointer(void* value, void* expected) { + if (value != expected) { + fprintf(stderr, "Expected %p got %p\n", expected, value); + return 1; + } + + return 0; +} + +static cairo_user_data_key_t destruction_key; +static void SurfaceDestroyNotifier(void* data) { *(int*)data = 1; } + +static int TestNewSurface() { + int failures = 0; + int destroyed = 0; + + RefPtr<gfxASurface> s = + new gfxImageSurface(mozilla::gfx::IntSize(10, 10), + mozilla::gfx::SurfaceFormat::A8R8G8B8_UINT32); + cairo_surface_t* cs = s->CairoSurface(); + + cairo_surface_set_user_data(cs, &destruction_key, &destroyed, + SurfaceDestroyNotifier); + + failures += CheckInt(GetASurfaceRefCount(s.get()), 1); + failures += CheckInt(cairo_surface_get_reference_count(cs), 1); + failures += CheckInt(destroyed, 0); + + cairo_surface_reference(cs); + + failures += CheckInt(GetASurfaceRefCount(s.get()), 2); + failures += CheckInt(cairo_surface_get_reference_count(cs), 2); + failures += CheckInt(destroyed, 0); + + gfxASurface* savedWrapper = s.get(); + + s = nullptr; + + failures += CheckInt(cairo_surface_get_reference_count(cs), 1); + failures += CheckInt(destroyed, 0); + + s = gfxASurface::Wrap(cs); + + failures += CheckPointer(s.get(), savedWrapper); + failures += CheckInt(GetASurfaceRefCount(s.get()), 2); + failures += CheckInt(cairo_surface_get_reference_count(cs), 2); + failures += CheckInt(destroyed, 0); + + cairo_surface_destroy(cs); + + failures += CheckInt(GetASurfaceRefCount(s.get()), 1); + failures += CheckInt(cairo_surface_get_reference_count(cs), 1); + failures += CheckInt(destroyed, 0); + + s = nullptr; + + failures += CheckInt(destroyed, 1); + + return failures; +} + +static int TestExistingSurface() { + int failures = 0; + int destroyed = 0; + + cairo_surface_t* cs = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 10, 10); + + cairo_surface_set_user_data(cs, &destruction_key, &destroyed, + SurfaceDestroyNotifier); + + failures += CheckInt(cairo_surface_get_reference_count(cs), 1); + failures += CheckInt(destroyed, 0); + + RefPtr<gfxASurface> s = gfxASurface::Wrap(cs); + + failures += CheckInt(GetASurfaceRefCount(s.get()), 2); + + cairo_surface_reference(cs); + + failures += CheckInt(GetASurfaceRefCount(s.get()), 3); + failures += CheckInt(cairo_surface_get_reference_count(cs), 3); + failures += CheckInt(destroyed, 0); + + gfxASurface* savedWrapper = s.get(); + + s = nullptr; + + failures += CheckInt(cairo_surface_get_reference_count(cs), 2); + failures += CheckInt(destroyed, 0); + + s = gfxASurface::Wrap(cs); + + failures += CheckPointer(s.get(), savedWrapper); + failures += CheckInt(GetASurfaceRefCount(s.get()), 3); + failures += CheckInt(cairo_surface_get_reference_count(cs), 3); + failures += CheckInt(destroyed, 0); + + cairo_surface_destroy(cs); + + failures += CheckInt(GetASurfaceRefCount(s.get()), 2); + failures += CheckInt(cairo_surface_get_reference_count(cs), 2); + failures += CheckInt(destroyed, 0); + + s = nullptr; + + failures += CheckInt(cairo_surface_get_reference_count(cs), 1); + failures += CheckInt(destroyed, 0); + + cairo_surface_destroy(cs); + + failures += CheckInt(destroyed, 1); + + return failures; +} + +TEST(Gfx, SurfaceRefCount) +{ + int fail; + + fail = TestNewSurface(); + EXPECT_TRUE(fail == 0) << "TestNewSurface: " << fail << " failures"; + fail = TestExistingSurface(); + EXPECT_TRUE(fail == 0) << "TestExistingSurface: " << fail << " failures"; +} diff --git a/gfx/tests/gtest/moz.build b/gfx/tests/gtest/moz.build new file mode 100644 index 0000000000..7c79a84163 --- /dev/null +++ b/gfx/tests/gtest/moz.build @@ -0,0 +1,75 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +UNIFIED_SOURCES += [ + "gfxSurfaceRefCountTest.cpp", + "MockWidget.cpp", + "PolygonTestUtils.cpp", + "TestArena.cpp", + "TestArrayView.cpp", + "TestBSPTree.cpp", + "TestBufferRotation.cpp", + "TestConfigManager.cpp", + "TestCoord.cpp", + "TestGfxWidgets.cpp", + "TestMatrix.cpp", + "TestMoz2D.cpp", + "TestPolygon.cpp", + "TestQcms.cpp", + "TestRegion.cpp", + "TestSkipChars.cpp", + "TestSwizzle.cpp", + "TestTextures.cpp", + "TestTreeTraversal.cpp", +] + +# skip the test on windows10-aarch64 due to perma-crash - bug 1544961 +if not (CONFIG["OS_TARGET"] == "WINNT" and CONFIG["TARGET_CPU"] == "aarch64"): + UNIFIED_SOURCES += [ + "TestVsync.cpp", + ] + +if CONFIG["OS_TARGET"] != "Android": + UNIFIED_SOURCES += [ + "TestRect.cpp", + ] + +UNIFIED_SOURCES += [ + "/gfx/2d/unittest/%s" % p + for p in [ + "TestBase.cpp", + "TestBugs.cpp", + "TestCairo.cpp", + "TestPoint.cpp", + "TestScaling.cpp", + ] +] + +UNIFIED_SOURCES += [ + "/gfx/ots/tests/cff_charstring_test.cc", +] + +# ICC profiles used for verifying QCMS transformations. The copyright +# notice embedded in the profiles should be reviewed to ensure there are +# no known restrictions on distribution. +TEST_HARNESS_FILES.gtest += [ + "../../qcms/profiles/lcms_samsung_syncmaster.icc", + "../../qcms/profiles/lcms_thinkpad_w540.icc", +] + +include("/ipc/chromium/chromium-config.mozbuild") + +LOCAL_INCLUDES += [ + "/gfx/2d", + "/gfx/2d/unittest", + "/gfx/cairo/cairo/src", + "/gfx/config", + "/gfx/layers", + "/gfx/ots/src", + "/gfx/qcms", +] + +FINAL_LIBRARY = "xul-gtest" |