Bug 1236043 - Add a TiledRegion class. r=jrmuizel
authorMarkus Stange <mstange@themasta.com>
Mon, 18 Apr 2016 13:48:58 -0400
changeset 331523 62108d9ec45989978eeeeb75eb9de66bf773055f
parent 331522 195696ac5a244b0afaffa6bec188efb4cecc7994
child 331524 9116ef367e02ec9446569fc13f8d95b9220e3ee3
push id6048
push userkmoir@mozilla.com
push dateMon, 06 Jun 2016 19:02:08 +0000
treeherdermozilla-beta@46d72a56c57d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjrmuizel
bugs1236043
milestone48.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1236043 - Add a TiledRegion class. r=jrmuizel MozReview-Commit-ID: Fb0kBGo3yYo
gfx/src/TiledRegion.cpp
gfx/src/TiledRegion.h
gfx/src/moz.build
gfx/tests/gtest/TestRegion.cpp
new file mode 100644
--- /dev/null
+++ b/gfx/src/TiledRegion.cpp
@@ -0,0 +1,357 @@
+/* -*- 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 "TiledRegion.h"
+
+#include <algorithm>
+
+#include "mozilla/fallible.h"
+
+namespace mozilla {
+namespace gfx {
+
+static const int32_t kTileSize = 256;
+
+/**
+ * TiledRegionImpl stores an array of non-empty rectangles (pixman_box32_ts) to
+ * represent the region. Each rectangle is contained in a single tile;
+ * rectangles never cross tile boundaries. The rectangles are sorted by their
+ * tile's origin in top-to-bottom, left-to-right order.
+ * (Note that this can mean that a rectangle r1 can come before another
+ * rectangle r2 even if r2.y1 < r1.y1, as long as the two rects are in the same
+ * row of tiles and r1.x1 < r2.x1.)
+ * Empty tiles take up no space in the array - there is no rectangle stored for
+ * them. As a result, any algorithm that needs to deal with empty tiles will
+ * iterate through the mRects array and compare the positions of two
+ * consecutive rects to figure out whether there are any empty tiles between
+ * them.
+ */
+
+static pixman_box32_t
+IntersectionOfNonEmptyBoxes(const pixman_box32_t& aBox1,
+                            const pixman_box32_t& aBox2)
+{
+  return pixman_box32_t {
+    std::max(aBox1.x1, aBox2.x1),
+    std::max(aBox1.y1, aBox2.y1),
+    std::min(aBox1.x2, aBox2.x2),
+    std::min(aBox1.y2, aBox2.y2)
+  };
+}
+
+// A TileIterator points to a specific tile inside a certain tile range, or to
+// the end of the tile range. Advancing a TileIterator will move to the next
+// tile inside the range (or to the range end). The next tile is either the
+// tile to the right of the current one, or the first tile of the next tile
+// row if the current tile is already the last tile in the row.
+class TileIterator {
+public:
+  TileIterator(const pixman_box32_t& aTileBounds, const IntPoint& aPosition)
+    : mTileBounds(aTileBounds)
+    , mPos(aPosition)
+  {}
+
+  bool operator!=(const TileIterator& aOther) { return mPos != aOther.mPos; }
+  bool operator==(const TileIterator& aOther) { return mPos == aOther.mPos; }
+
+  IntPoint operator*() const { return mPos; }
+
+  const TileIterator& operator++() {
+    mPos.x += kTileSize;
+    if (mPos.x >= mTileBounds.x2) {
+      mPos.x = mTileBounds.x1;
+      mPos.y += kTileSize;
+    }
+    return *this;
+  }
+
+  TileIterator& operator=(const IntPoint& aPosition)
+  {
+    mPos = aPosition;
+    return *this;
+  }
+
+  bool IsBeforeTileContainingPoint(const IntPoint& aPoint) const
+  {
+    return (mPos.y + kTileSize) <= aPoint.y  ||
+      (mPos.y <= aPoint.y && (mPos.x + kTileSize) <= aPoint.x);
+  }
+
+  bool IsAtTileContainingPoint(const IntPoint& aPoint) const
+  {
+    return mPos.y <= aPoint.y && aPoint.y < (mPos.y + kTileSize) &&
+           mPos.x <= aPoint.x && aPoint.x < (mPos.x + kTileSize);
+
+  }
+
+  pixman_box32_t IntersectionWith(const pixman_box32_t& aRect) const
+  {
+    pixman_box32_t tile = { mPos.x, mPos.y,
+                            mPos.x + kTileSize, mPos.y + kTileSize };
+    return IntersectionOfNonEmptyBoxes(tile, aRect);
+  }
+
+private:
+  const pixman_box32_t& mTileBounds;
+  IntPoint mPos;
+};
+
+// A TileRange describes a range of tiles contained inside a certain tile
+// bounds (which is a rectangle that includes all tiles that you're
+// interested in). The tile range can start and end at any point inside a
+// tile row.
+// The tile range end is described by the tile that starts at the bottom
+// left corner of the tile bounds, i.e. the first tile under the tile
+// bounds.
+class TileRange {
+public:
+  // aTileBounds, aStart and aEnd need to be aligned with the tile grid.
+  TileRange(const pixman_box32_t& aTileBounds,
+            const IntPoint& aStart, const IntPoint& aEnd)
+    : mTileBounds(aTileBounds)
+    , mStart(aStart)
+    , mEnd(aEnd)
+  {}
+  // aTileBounds needs to be aligned with the tile grid.
+  explicit TileRange(const pixman_box32_t& aTileBounds)
+    : mTileBounds(aTileBounds)
+    , mStart(mTileBounds.x1, mTileBounds.y1)
+    , mEnd(mTileBounds.x1, mTileBounds.y2)
+  {}
+
+  TileIterator Begin() const { return TileIterator(mTileBounds, mStart); }
+  TileIterator End() const { return TileIterator(mTileBounds, mEnd); }
+
+  // The number of tiles in this tile range.
+  size_t Length() const
+  {
+    if (mEnd.y == mStart.y) {
+      return (mEnd.x - mStart.x) / kTileSize;
+    }
+    size_t numberOfFullRows = (mEnd.y - mStart.y) / kTileSize - 1;
+    return ((mTileBounds.x2 - mStart.x) +
+            (mTileBounds.x2 - mTileBounds.x1) * numberOfFullRows +
+            (mEnd.x - mTileBounds.x1)) / kTileSize;
+  }
+
+  // If aTileOrigin does not describe a tile inside our tile bounds, move it
+  // to the next tile that you'd encounter by "advancing" a tile iterator
+  // inside these tile bounds. If aTileOrigin is after the last tile inside
+  // our tile bounds, move it to the range end tile.
+  // The result of this method is a valid end tile for a tile range with our
+  // tile bounds.
+  IntPoint MoveIntoBounds(const IntPoint& aTileOrigin) const
+  {
+    IntPoint p = aTileOrigin;
+    if (p.x < mTileBounds.x1) {
+      p.x = mTileBounds.x1;
+    } else if (p.x >= mTileBounds.x2) {
+      p.x = mTileBounds.x1;
+      p.y += kTileSize;
+    }
+    if (p.y < mTileBounds.y1) {
+      p.y = mTileBounds.y1;
+      p.x = mTileBounds.x1;
+    } else if (p.y >= mTileBounds.y2) {
+      // There's only one valid state after the end of the tile range, and that's
+      // the bottom left point of the tile bounds.
+      p.x = mTileBounds.x1;
+      p.y = mTileBounds.y2;
+    }
+    return p;
+  }
+
+private:
+  const pixman_box32_t& mTileBounds;
+  const IntPoint mStart;
+  const IntPoint mEnd;
+};
+
+static IntPoint
+TileContainingPoint(const IntPoint& aPoint)
+{
+  return IntPoint(RoundDownToMultiple(aPoint.x, kTileSize),
+                  RoundDownToMultiple(aPoint.y, kTileSize));
+}
+
+enum class IterationAction : uint8_t {
+  CONTINUE,
+  STOP
+};
+
+enum class IterationEndReason : uint8_t {
+  NOT_STOPPED,
+  STOPPED
+};
+
+template<
+  typename HandleEmptyTilesFunction,
+  typename HandleNonEmptyTileFunction,
+  typename RectArrayT>
+IterationEndReason ProcessIntersectedTiles(const pixman_box32_t& aRect,
+                                           RectArrayT& aRectArray,
+                                           HandleEmptyTilesFunction aHandleEmptyTiles,
+                                           HandleNonEmptyTileFunction aHandleNonEmptyTile)
+{
+  pixman_box32_t tileBounds = {
+    RoundDownToMultiple(aRect.x1, kTileSize),
+    RoundDownToMultiple(aRect.y1, kTileSize),
+    RoundUpToMultiple(aRect.x2, kTileSize),
+    RoundUpToMultiple(aRect.y2, kTileSize)
+  };
+
+  TileRange tileRange(tileBounds);
+  TileIterator rangeEnd = tileRange.End();
+
+  // tileIterator points to the next tile in tileRange, or to rangeEnd if we're
+  // done.
+  TileIterator tileIterator = tileRange.Begin();
+
+  // We iterate over the rectangle array. Depending on the position of the
+  // rectangle we encounter, we may need to advance tileIterator by zero, one,
+  // or more tiles:
+  //  - Zero if the rectangle we encountered is outside the tiles that
+  //    intersect aRect.
+  //  - One if the rectangle is in the exact tile that we're interested in next
+  //    (i.e. the tile that tileIterator points at).
+  //  - More than one if the encountered rectangle is in a tile that's further
+  //    to the right or to the bottom than tileIterator. In that case there is
+  //    at least one empty tile between the last rectangle we encountered and
+  //    the current one.
+  for (size_t i = 0; i < aRectArray.Length() && tileIterator != rangeEnd; i++) {
+    MOZ_ASSERT(aRectArray[i].x1 < aRectArray[i].x2 && aRectArray[i].y1 < aRectArray[i].y2, "empty rect");
+    IntPoint rectOrigin(aRectArray[i].x1, aRectArray[i].y1);
+    if (tileIterator.IsBeforeTileContainingPoint(rectOrigin)) {
+      IntPoint tileOrigin = TileContainingPoint(rectOrigin);
+      IntPoint afterEmptyTiles = tileRange.MoveIntoBounds(tileOrigin);
+      TileRange emptyTiles(tileBounds, *tileIterator, afterEmptyTiles);
+      if (aHandleEmptyTiles(aRectArray, i, emptyTiles) == IterationAction::STOP) {
+        return IterationEndReason::STOPPED;
+      }
+      tileIterator = afterEmptyTiles;
+      if (tileIterator == rangeEnd) {
+        return IterationEndReason::NOT_STOPPED;
+      }
+    }
+    if (tileIterator.IsAtTileContainingPoint(rectOrigin)) {
+      pixman_box32_t rectIntersection = tileIterator.IntersectionWith(aRect);
+      if (aHandleNonEmptyTile(aRectArray, i, rectIntersection) == IterationAction::STOP) {
+        return IterationEndReason::STOPPED;
+      }
+      ++tileIterator;
+    }
+  }
+
+  if (tileIterator != rangeEnd) {
+    // We've looked at all of our existing rectangles but haven't covered all
+    // of the tiles that we're interested in yet. So we need to deal with the
+    // remaining tiles now.
+    size_t endIndex = aRectArray.Length();
+    TileRange emptyTiles(tileBounds, *tileIterator, *rangeEnd);
+    if (aHandleEmptyTiles(aRectArray, endIndex, emptyTiles) == IterationAction::STOP) {
+      return IterationEndReason::STOPPED;
+    }
+  }
+  return IterationEndReason::NOT_STOPPED;
+}
+
+static pixman_box32_t
+UnionBoundsOfNonEmptyBoxes(const pixman_box32_t& aBox1,
+                           const pixman_box32_t& aBox2)
+{
+  return { std::min(aBox1.x1, aBox2.x1),
+           std::min(aBox1.y1, aBox2.y1),
+           std::max(aBox1.x2, aBox2.x2),
+           std::max(aBox1.y2, aBox2.y2) };
+}
+
+// Returns true when adding the rectangle was successful, and false if
+// allocation failed.
+// When this returns false, our internal state might not be consistent and we
+// need to be cleared.
+bool
+TiledRegionImpl::AddRect(const pixman_box32_t& aRect)
+{
+  // We are adding a rectangle that can span multiple tiles.
+  // For each empty tile that aRect intersects, we need to add the intersection
+  // of aRect with that tile to mRects, respecting the order of mRects.
+  // For each tile that already has a rectangle, we need to enlarge that
+  // existing rectangle to include the intersection of aRect with the tile.
+  return ProcessIntersectedTiles(aRect, mRects,
+    [&aRect](nsTArray<pixman_box32_t>& rects, size_t& rectIndex, TileRange emptyTiles) {
+      if (!rects.InsertElementsAt(rectIndex, emptyTiles.Length(), fallible)) {
+        return IterationAction::STOP;
+      }
+      for (TileIterator tileIt = emptyTiles.Begin();
+           tileIt != emptyTiles.End();
+           ++tileIt, ++rectIndex) {
+        rects[rectIndex] = tileIt.IntersectionWith(aRect);
+      }
+      return IterationAction::CONTINUE;
+    },
+    [](nsTArray<pixman_box32_t>& rects, size_t rectIndex, const pixman_box32_t& rectIntersectionWithTile) {
+      rects[rectIndex] =
+        UnionBoundsOfNonEmptyBoxes(rects[rectIndex], rectIntersectionWithTile);
+      return IterationAction::CONTINUE;
+    }) == IterationEndReason::NOT_STOPPED;
+}
+
+static bool
+NonEmptyBoxesIntersect(const pixman_box32_t& aBox1, const pixman_box32_t& aBox2)
+{
+  return aBox1.x1 < aBox2.x2 && aBox2.x1 < aBox1.x2 &&
+         aBox1.y1 < aBox2.y2 && aBox2.y1 < aBox1.y2;
+}
+
+bool
+TiledRegionImpl::Intersects(const pixman_box32_t& aRect) const
+{
+  // aRect intersects this region if it intersects any of our rectangles.
+  return ProcessIntersectedTiles(aRect, mRects,
+    [](const nsTArray<pixman_box32_t>& rects, size_t& rectIndex, TileRange emptyTiles) {
+      // Ignore empty tiles and keep on iterating.
+      return IterationAction::CONTINUE;
+    },
+    [](const nsTArray<pixman_box32_t>& rects, size_t rectIndex, const pixman_box32_t& rectIntersectionWithTile) {
+      if (NonEmptyBoxesIntersect(rects[rectIndex], rectIntersectionWithTile)) {
+        // Found an intersecting rectangle, so aRect intersects this region.
+        return IterationAction::STOP;
+      }
+      return IterationAction::CONTINUE;
+    }) == IterationEndReason::STOPPED;
+}
+
+static bool
+NonEmptyBoxContainsNonEmptyBox(const pixman_box32_t& aBox1, const pixman_box32_t& aBox2)
+{
+  return aBox1.x1 <= aBox2.x1 && aBox2.x2 <= aBox1.x2 &&
+         aBox1.y1 <= aBox2.y1 && aBox2.y2 <= aBox1.y2;
+}
+
+bool
+TiledRegionImpl::Contains(const pixman_box32_t& aRect) const
+{
+  // aRect is contained in this region if aRect does not intersect any empty
+  // tiles and, for each non-empty tile, if the intersection of aRect with that
+  // tile is contained in the existing rectangle we have in that tile.
+  return ProcessIntersectedTiles(aRect, mRects,
+    [](const nsTArray<pixman_box32_t>& rects, size_t& rectIndex, TileRange emptyTiles) {
+      // Found an empty tile that intersects aRect, so aRect is not contained
+      // in this region.
+      return IterationAction::STOP;
+    },
+    [](const nsTArray<pixman_box32_t>& rects, size_t rectIndex, const pixman_box32_t& rectIntersectionWithTile) {
+      if (!NonEmptyBoxContainsNonEmptyBox(rects[rectIndex], rectIntersectionWithTile)) {
+        // Our existing rectangle in this tile does not cover the part of aRect that
+        // intersects this tile, so aRect is not contained in this region.
+        return IterationAction::STOP;
+      }
+      return IterationAction::CONTINUE;
+    }) == IterationEndReason::NOT_STOPPED;
+}
+
+} // namespace gfx
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/gfx/src/TiledRegion.h
@@ -0,0 +1,198 @@
+/* -*- 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 MOZILLA_GFX_TILEDREGION_H_
+#define MOZILLA_GFX_TILEDREGION_H_
+
+#include "mozilla/ArrayView.h"
+#include "mozilla/gfx/Rect.h"
+#include "mozilla/Move.h"
+#include "nsRegion.h"
+#include "pixman.h"
+
+namespace mozilla {
+namespace gfx {
+
+// See TiledRegion.cpp for documentation on TiledRegionImpl.
+class TiledRegionImpl {
+public:
+  void Clear() { mRects.Clear(); }
+  bool AddRect(const pixman_box32_t& aRect);
+  bool Intersects(const pixman_box32_t& aRect) const;
+  bool Contains(const pixman_box32_t& aRect) const;
+  operator ArrayView<pixman_box32_t>() const { return ArrayView<pixman_box32_t>(mRects); }
+
+private:
+  nsTArray<pixman_box32_t> mRects;
+};
+
+/**
+ * A auto-simplifying region type that supports one rectangle per tile.
+ * The virtual tile grid is anchored at (0, 0) and has quadratic tiles whose
+ * size is hard-coded as kTileSize in TiledRegion.cpp.
+ * A TiledRegion starts out empty. You can add rectangles or (regular) regions
+ * into it by calling Add(). Add() is a mutating union operation (similar to
+ * OrWith on nsRegion) that's *not* exact, because it will enlarge the region as
+ * necessary to satisfy the "one rectangle per tile" requirement.
+ * Tiled regions convert implicitly to the underlying regular region type.
+ * The only way to remove parts from a TiledRegion is by calling SetEmpty().
+ */
+template<typename RegionT>
+class TiledRegion {
+public:
+  typedef typename RegionT::RectType RectT;
+
+  TiledRegion()
+    : mCoversBounds(false)
+  {}
+
+  TiledRegion(const TiledRegion& aOther)
+    : mBounds(aOther.mBounds)
+    , mImpl(aOther.mImpl)
+    , mCoversBounds(false)
+  {}
+
+  TiledRegion(TiledRegion&& aOther)
+    : mBounds(aOther.mBounds)
+    , mImpl(Move(aOther.mImpl))
+    , mCoversBounds(false)
+  {}
+
+  RegionT GetRegion() const
+  {
+    if (mBounds.IsEmpty()) {
+      return RegionT();
+    }
+    if (mCoversBounds) {
+      // Rect limit hit or allocation failed, treat as 1 rect.
+      return RegionT(mBounds);
+    }
+    return RegionT(mImpl);
+  }
+
+  TiledRegion& operator=(const TiledRegion& aOther)
+  {
+    if (&aOther != this) {
+      mBounds = aOther.mBounds;
+      mImpl = aOther.mImpl;
+      mCoversBounds = aOther.mCoversBounds;
+    }
+    return *this;
+  }
+
+  void Add(const RectT& aRect)
+  {
+    if (aRect.IsEmpty()) {
+      return;
+    }
+
+    mBounds = mBounds.Union(aRect);
+
+    if (mCoversBounds) {
+      return;
+    }
+    if (ExceedsMaximumSize()) {
+      FallBackToBounds();
+      return;
+    }
+
+    if (!mImpl.AddRect(RectToBox(aRect))) {
+      FallBackToBounds();
+    }
+  }
+
+  void Add(const RegionT& aRegion)
+  {
+    mBounds = mBounds.Union(aRegion.GetBounds());
+
+    if (mCoversBounds) {
+      return;
+    }
+    if (ExceedsMaximumSize()) {
+      FallBackToBounds();
+      return;
+    }
+
+    for (auto iter = aRegion.RectIter(); !iter.Done(); iter.Next()) {
+      RectT r = iter.Get();
+      MOZ_ASSERT(!r.IsEmpty());
+      if (!mImpl.AddRect(RectToBox(r))) {
+        FallBackToBounds();
+        return;
+      }
+    }
+  }
+
+  bool IsEmpty() const { return mBounds.IsEmpty(); }
+
+  void SetEmpty()
+  {
+    mBounds.SetEmpty();
+    mImpl.Clear();
+    mCoversBounds = false;
+  }
+
+  RectT GetBounds() const { return mBounds; }
+
+  bool Intersects(const RectT& aRect) const
+  {
+    if (!mBounds.Intersects(aRect)) {
+      return false;
+    }
+    if (mCoversBounds) {
+      return true;
+    }
+
+    return mImpl.Intersects(RectToBox(aRect));
+  }
+
+  bool Contains(const RectT& aRect) const
+  {
+    if (!mBounds.Contains(aRect)) {
+      return false;
+    }
+    if (mCoversBounds) {
+      return true;
+    }
+    return mImpl.Contains(RectToBox(aRect));
+  }
+
+private:
+
+  bool ExceedsMaximumSize() const
+  {
+    // This stops us from allocating insane numbers of tiles.
+    return mBounds.width >= 50 * 256 || mBounds.height >= 50 * 256;
+  }
+
+  void FallBackToBounds()
+  {
+    mCoversBounds = true;
+    mImpl.Clear();
+  }
+
+  static pixman_box32_t RectToBox(const RectT& aRect)
+  {
+    return { aRect.x, aRect.y, aRect.XMost(), aRect.YMost() };
+  }
+
+  RectT mBounds;
+  TiledRegionImpl mImpl;
+
+  // mCoversBounds is true if we bailed out due to a large number of tiles.
+  // mCoversBounds being true means that this TiledRegion is just a simple
+  // rectangle (our mBounds).
+  // Once set to true, the TiledRegion will stay in this state until SetEmpty
+  // is called.
+  bool mCoversBounds;
+};
+
+typedef TiledRegion<IntRegion> TiledIntRegion;
+
+} // namespace gfx
+} // namespace mozilla
+
+#endif /* MOZILLA_GFX_TILEDREGION_H_ */
--- a/gfx/src/moz.build
+++ b/gfx/src/moz.build
@@ -41,16 +41,20 @@ EXPORTS += [
     'RegionBuilder.h',
 ]
 
 EXPORTS.mozilla += [
     'AppUnits.h',
     'ArrayView.h',
 ]
 
+EXPORTS.mozilla.gfx += [
+    'TiledRegion.h',
+]
+
 if CONFIG['MOZ_X11']:
     EXPORTS.mozilla += ['X11Util.h']
     SOURCES += [
         'X11Util.cpp',
     ]
 
 UNIFIED_SOURCES += [
     'DriverCrashGuard.cpp',
@@ -61,16 +65,17 @@ UNIFIED_SOURCES += [
     'nsFont.cpp',
     'nsFontMetrics.cpp',
     'nsRect.cpp',
     'nsRegion.cpp',
     'nsScriptableRegion.cpp',
     'nsThebesFontEnumerator.cpp',
     'nsThebesGfxFactory.cpp',
     'nsTransform2D.cpp',
+    'TiledRegion.cpp',
 ]
 
 # nsDeviceContext.cpp cannot be built in unified mode because it pulls in OS X system headers.
 SOURCES += [
     'nsDeviceContext.cpp',
 ]
 
 include('/ipc/chromium/chromium-config.mozbuild')
--- a/gfx/tests/gtest/TestRegion.cpp
+++ b/gfx/tests/gtest/TestRegion.cpp
@@ -6,18 +6,20 @@
 #include <algorithm>
 
 #include "PingPongRegion.h"
 #include "gtest/gtest.h"
 #include "gtest/MozGTestBench.h"
 #include "nsRect.h"
 #include "nsRegion.h"
 #include "RegionBuilder.h"
+#include "mozilla/gfx/TiledRegion.h"
 
 using namespace std;
+using namespace mozilla::gfx;
 
 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
@@ -592,16 +594,107 @@ TEST(Gfx, PingPongRegion) {
     for (size_t i = 0; i < size; i++) {
       ar.SubOut(rects[i]);
       r.SubOut(rects[i]);
       EXPECT_TRUE(ar.Region().IsEqual(r));
     }
   }
 }
 
+// The TiledRegion tests use nsIntRect / IntRegion because nsRect doesn't have
+// InflateToMultiple which is required by TiledRegion.
+TEST(Gfx, TiledRegionNoSimplification2Rects) {
+  // Add two rectangles, both rectangles are completely inside
+  // different tiles.
+  nsIntRegion region;
+  region.OrWith(nsIntRect(50, 50, 50, 50));
+  region.OrWith(nsIntRect(300, 50, 50, 50));
+
+  TiledIntRegion tiledRegion;
+  tiledRegion.Add(nsIntRect(50, 50, 50, 50));
+  tiledRegion.Add(nsIntRect(300, 50, 50, 50));
+
+  // No simplification should have happened.
+  EXPECT_TRUE(region.IsEqual(tiledRegion.GetRegion()));
+}
+
+TEST(Gfx, TiledRegionNoSimplification1Region) {
+  // Add two rectangles, both rectangles are completely inside
+  // different tiles.
+  nsIntRegion region;
+  region.OrWith(nsIntRect(50, 50, 50, 50));
+  region.OrWith(nsIntRect(300, 50, 50, 50));
+
+  TiledIntRegion tiledRegion;
+  tiledRegion.Add(region);
+
+  // No simplification should have happened.
+  EXPECT_TRUE(region.IsEqual(tiledRegion.GetRegion()));
+}
+
+TEST(Gfx, TiledRegionWithSimplification3Rects) {
+  // Add three rectangles. The first two rectangles are completely inside
+  // different tiles, but the third rectangle intersects both tiles.
+  TiledIntRegion tiledRegion;
+  tiledRegion.Add(nsIntRect(50, 50, 50, 50));
+  tiledRegion.Add(nsIntRect(300, 50, 50, 50));
+  tiledRegion.Add(nsIntRect(250, 70, 10, 10));
+
+  // Both tiles should have simplified their rectangles, and those two
+  // rectangles are adjacent to each other, so they just build up one rect.
+  EXPECT_TRUE(tiledRegion.GetRegion().IsEqual(nsIntRect(50, 50, 300, 50)));
+}
+
+TEST(Gfx, TiledRegionWithSimplification1Region) {
+  // Add three rectangles. The first two rectangles are completely inside
+  // different tiles, but the third rectangle intersects both tiles.
+  nsIntRegion region;
+  region.OrWith(nsIntRect(50, 50, 50, 50));
+  region.OrWith(nsIntRect(300, 50, 50, 50));
+  region.OrWith(nsIntRect(250, 70, 10, 10));
+
+  TiledIntRegion tiledRegion;
+  tiledRegion.Add(region);
+
+  // Both tiles should have simplified their rectangles, and those two
+  // rectangles are adjacent to each other, so they just build up one rect.
+  EXPECT_TRUE(tiledRegion.GetRegion().IsEqual(nsIntRect(50, 50, 300, 50)));
+}
+
+TEST(Gfx, TiledRegionContains) {
+  // Add three rectangles. The first two rectangles are completely inside
+  // different tiles, but the third rectangle intersects both tiles.
+  TiledIntRegion tiledRegion;
+  tiledRegion.Add(nsIntRect(50, 50, 50, 50));
+  tiledRegion.Add(nsIntRect(300, 50, 50, 50));
+  tiledRegion.Add(nsIntRect(250, 70, 10, 10));
+
+  // Both tiles should have simplified their rectangles, and those two
+  // rectangles are adjacent to each other, so they just build up one rect.
+  EXPECT_TRUE(tiledRegion.Contains(nsIntRect(50, 50, 300, 50)));
+  EXPECT_TRUE(tiledRegion.Contains(nsIntRect(50, 50, 50, 50)));
+  EXPECT_FALSE(tiledRegion.Contains(nsIntRect(50, 50, 301, 50)));
+}
+
+TEST(Gfx, TiledRegionIntersects) {
+  // Add three rectangles. The first two rectangles are completely inside
+  // different tiles, but the third rectangle intersects both tiles.
+  TiledIntRegion tiledRegion;
+  tiledRegion.Add(nsIntRect(50, 50, 50, 50));
+  tiledRegion.Add(nsIntRect(300, 50, 50, 50));
+  tiledRegion.Add(nsIntRect(250, 70, 10, 10));
+
+  // Both tiles should have simplified their rectangles, and those two
+  // rectangles are adjacent to each other, so they just build up one rect.
+  EXPECT_TRUE(tiledRegion.Intersects(nsIntRect(50, 50, 300, 50)));
+  EXPECT_TRUE(tiledRegion.Intersects(nsIntRect(200, 10, 10, 50)));
+  EXPECT_TRUE(tiledRegion.Intersects(nsIntRect(50, 50, 301, 50)));
+  EXPECT_FALSE(tiledRegion.Intersects(nsIntRect(0, 0, 50, 500)));
+}
+
 MOZ_GTEST_BENCH(GfxBench, RegionOr, []{
   const int size = 5000;
 
   nsRegion r;
   for (int i = 0; i < size; i++) {
     r = r.Or(r, nsRect(i, i, i + 10, i + 10));
   }