1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 #include "TiledRegion.h"
8 
9 #include <algorithm>
10 
11 #include "mozilla/fallible.h"
12 
13 namespace mozilla {
14 namespace gfx {
15 
16 static const int32_t kTileSize = 256;
17 static const size_t kMaxTiles = 1000;
18 
19 /**
20  * TiledRegionImpl stores an array of non-empty rectangles (pixman_box32_ts) to
21  * represent the region. Each rectangle is contained in a single tile;
22  * rectangles never cross tile boundaries. The rectangles are sorted by their
23  * tile's origin in top-to-bottom, left-to-right order.
24  * (Note that this can mean that a rectangle r1 can come before another
25  * rectangle r2 even if r2.y1 < r1.y1, as long as the two rects are in the same
26  * row of tiles and r1.x1 < r2.x1.)
27  * Empty tiles take up no space in the array - there is no rectangle stored for
28  * them. As a result, any algorithm that needs to deal with empty tiles will
29  * iterate through the mRects array and compare the positions of two
30  * consecutive rects to figure out whether there are any empty tiles between
31  * them.
32  */
33 
IntersectionOfNonEmptyBoxes(const pixman_box32_t & aBox1,const pixman_box32_t & aBox2)34 static pixman_box32_t IntersectionOfNonEmptyBoxes(const pixman_box32_t& aBox1,
35                                                   const pixman_box32_t& aBox2) {
36   return pixman_box32_t{
37       std::max(aBox1.x1, aBox2.x1), std::max(aBox1.y1, aBox2.y1),
38       std::min(aBox1.x2, aBox2.x2), std::min(aBox1.y2, aBox2.y2)};
39 }
40 
41 // A TileIterator points to a specific tile inside a certain tile range, or to
42 // the end of the tile range. Advancing a TileIterator will move to the next
43 // tile inside the range (or to the range end). The next tile is either the
44 // tile to the right of the current one, or the first tile of the next tile
45 // row if the current tile is already the last tile in the row.
46 class TileIterator {
47  public:
TileIterator(const pixman_box32_t & aTileBounds,const IntPoint & aPosition)48   TileIterator(const pixman_box32_t& aTileBounds, const IntPoint& aPosition)
49       : mTileBounds(aTileBounds), mPos(aPosition) {}
50 
operator !=(const TileIterator & aOther)51   bool operator!=(const TileIterator& aOther) { return mPos != aOther.mPos; }
operator ==(const TileIterator & aOther)52   bool operator==(const TileIterator& aOther) { return mPos == aOther.mPos; }
53 
operator *() const54   IntPoint operator*() const { return mPos; }
55 
operator ++()56   const TileIterator& operator++() {
57     mPos.x += kTileSize;
58     if (mPos.x >= mTileBounds.x2) {
59       mPos.x = mTileBounds.x1;
60       mPos.y += kTileSize;
61     }
62     return *this;
63   }
64 
operator =(const IntPoint & aPosition)65   TileIterator& operator=(const IntPoint& aPosition) {
66     mPos = aPosition;
67     return *this;
68   }
69 
IsBeforeTileContainingPoint(const IntPoint & aPoint) const70   bool IsBeforeTileContainingPoint(const IntPoint& aPoint) const {
71     return (mPos.y + kTileSize) <= aPoint.y ||
72            (mPos.y <= aPoint.y && (mPos.x + kTileSize) <= aPoint.x);
73   }
74 
IsAtTileContainingPoint(const IntPoint & aPoint) const75   bool IsAtTileContainingPoint(const IntPoint& aPoint) const {
76     return mPos.y <= aPoint.y && aPoint.y < (mPos.y + kTileSize) &&
77            mPos.x <= aPoint.x && aPoint.x < (mPos.x + kTileSize);
78   }
79 
IntersectionWith(const pixman_box32_t & aRect) const80   pixman_box32_t IntersectionWith(const pixman_box32_t& aRect) const {
81     pixman_box32_t tile = {mPos.x, mPos.y, mPos.x + kTileSize,
82                            mPos.y + kTileSize};
83     return IntersectionOfNonEmptyBoxes(tile, aRect);
84   }
85 
86  private:
87   const pixman_box32_t& mTileBounds;
88   IntPoint mPos;
89 };
90 
91 // A TileRange describes a range of tiles contained inside a certain tile
92 // bounds (which is a rectangle that includes all tiles that you're
93 // interested in). The tile range can start and end at any point inside a
94 // tile row.
95 // The tile range end is described by the tile that starts at the bottom
96 // left corner of the tile bounds, i.e. the first tile under the tile
97 // bounds.
98 class TileRange {
99  public:
100   // aTileBounds, aStart and aEnd need to be aligned with the tile grid.
TileRange(const pixman_box32_t & aTileBounds,const IntPoint & aStart,const IntPoint & aEnd)101   TileRange(const pixman_box32_t& aTileBounds, const IntPoint& aStart,
102             const IntPoint& aEnd)
103       : mTileBounds(aTileBounds), mStart(aStart), mEnd(aEnd) {}
104   // aTileBounds needs to be aligned with the tile grid.
TileRange(const pixman_box32_t & aTileBounds)105   explicit TileRange(const pixman_box32_t& aTileBounds)
106       : mTileBounds(aTileBounds),
107         mStart(mTileBounds.x1, mTileBounds.y1),
108         mEnd(mTileBounds.x1, mTileBounds.y2) {}
109 
Begin() const110   TileIterator Begin() const { return TileIterator(mTileBounds, mStart); }
End() const111   TileIterator End() const { return TileIterator(mTileBounds, mEnd); }
112 
113   // The number of tiles in this tile range.
Length() const114   size_t Length() const {
115     if (mEnd.y == mStart.y) {
116       return (mEnd.x - mStart.x) / kTileSize;
117     }
118     int64_t numberOfFullRows =
119         (((int64_t)mEnd.y - (int64_t)mStart.y) / kTileSize) - 1;
120     int64_t tilesInFirstRow =
121         ((int64_t)mTileBounds.x2 - (int64_t)mStart.x) / kTileSize;
122     int64_t tilesInLastRow =
123         ((int64_t)mEnd.x - (int64_t)mTileBounds.x1) / kTileSize;
124     int64_t tilesInFullRow =
125         ((int64_t)mTileBounds.x2 - (int64_t)mTileBounds.x1) / kTileSize;
126     int64_t total =
127         tilesInFirstRow + (tilesInFullRow * numberOfFullRows) + tilesInLastRow;
128     MOZ_ASSERT(total > 0);
129     // On 32bit systems the total may be larger than what fits in a size_t (4
130     // bytes), so clamp it to size_t's max value in that case.
131     return static_cast<uint64_t>(total) >=
132                    static_cast<uint64_t>(std::numeric_limits<size_t>::max())
133                ? std::numeric_limits<size_t>::max()
134                : static_cast<size_t>(total);
135   }
136 
137   // If aTileOrigin does not describe a tile inside our tile bounds, move it
138   // to the next tile that you'd encounter by "advancing" a tile iterator
139   // inside these tile bounds. If aTileOrigin is after the last tile inside
140   // our tile bounds, move it to the range end tile.
141   // The result of this method is a valid end tile for a tile range with our
142   // tile bounds.
MoveIntoBounds(const IntPoint & aTileOrigin) const143   IntPoint MoveIntoBounds(const IntPoint& aTileOrigin) const {
144     IntPoint p = aTileOrigin;
145     if (p.x < mTileBounds.x1) {
146       p.x = mTileBounds.x1;
147     } else if (p.x >= mTileBounds.x2) {
148       p.x = mTileBounds.x1;
149       p.y += kTileSize;
150     }
151     if (p.y < mTileBounds.y1) {
152       p.y = mTileBounds.y1;
153       p.x = mTileBounds.x1;
154     } else if (p.y >= mTileBounds.y2) {
155       // There's only one valid state after the end of the tile range, and
156       // that's the bottom left point of the tile bounds.
157       p.x = mTileBounds.x1;
158       p.y = mTileBounds.y2;
159     }
160     return p;
161   }
162 
163  private:
164   const pixman_box32_t& mTileBounds;
165   const IntPoint mStart;
166   const IntPoint mEnd;
167 };
168 
TileContainingPoint(const IntPoint & aPoint)169 static IntPoint TileContainingPoint(const IntPoint& aPoint) {
170   return IntPoint(RoundDownToMultiple(aPoint.x, kTileSize),
171                   RoundDownToMultiple(aPoint.y, kTileSize));
172 }
173 
174 enum class IterationAction : uint8_t { CONTINUE, STOP };
175 
176 enum class IterationEndReason : uint8_t { NOT_STOPPED, STOPPED };
177 
178 template <typename HandleEmptyTilesFunction,
179           typename HandleNonEmptyTileFunction, typename RectArrayT>
ProcessIntersectedTiles(const pixman_box32_t & aRect,RectArrayT & aRectArray,HandleEmptyTilesFunction aHandleEmptyTiles,HandleNonEmptyTileFunction aHandleNonEmptyTile)180 IterationEndReason ProcessIntersectedTiles(
181     const pixman_box32_t& aRect, RectArrayT& aRectArray,
182     HandleEmptyTilesFunction aHandleEmptyTiles,
183     HandleNonEmptyTileFunction aHandleNonEmptyTile) {
184   pixman_box32_t tileBounds = {RoundDownToMultiple(aRect.x1, kTileSize),
185                                RoundDownToMultiple(aRect.y1, kTileSize),
186                                RoundUpToMultiple(aRect.x2, kTileSize),
187                                RoundUpToMultiple(aRect.y2, kTileSize)};
188   if (tileBounds.x2 < tileBounds.x1 || tileBounds.y2 < tileBounds.y1) {
189     // RoundUpToMultiple probably overflowed. Bail out.
190     return IterationEndReason::STOPPED;
191   }
192 
193   TileRange tileRange(tileBounds);
194   TileIterator rangeEnd = tileRange.End();
195 
196   // tileIterator points to the next tile in tileRange, or to rangeEnd if we're
197   // done.
198   TileIterator tileIterator = tileRange.Begin();
199 
200   // We iterate over the rectangle array. Depending on the position of the
201   // rectangle we encounter, we may need to advance tileIterator by zero, one,
202   // or more tiles:
203   //  - Zero if the rectangle we encountered is outside the tiles that
204   //    intersect aRect.
205   //  - One if the rectangle is in the exact tile that we're interested in next
206   //    (i.e. the tile that tileIterator points at).
207   //  - More than one if the encountered rectangle is in a tile that's further
208   //    to the right or to the bottom than tileIterator. In that case there is
209   //    at least one empty tile between the last rectangle we encountered and
210   //    the current one.
211   for (size_t i = 0; i < aRectArray.Length() && tileIterator != rangeEnd; i++) {
212     MOZ_ASSERT(aRectArray[i].x1 < aRectArray[i].x2 &&
213                    aRectArray[i].y1 < aRectArray[i].y2,
214                "empty rect");
215     IntPoint rectOrigin(aRectArray[i].x1, aRectArray[i].y1);
216     if (tileIterator.IsBeforeTileContainingPoint(rectOrigin)) {
217       IntPoint tileOrigin = TileContainingPoint(rectOrigin);
218       IntPoint afterEmptyTiles = tileRange.MoveIntoBounds(tileOrigin);
219       TileRange emptyTiles(tileBounds, *tileIterator, afterEmptyTiles);
220       if (aHandleEmptyTiles(aRectArray, i, emptyTiles) ==
221           IterationAction::STOP) {
222         return IterationEndReason::STOPPED;
223       }
224       tileIterator = afterEmptyTiles;
225       if (tileIterator == rangeEnd) {
226         return IterationEndReason::NOT_STOPPED;
227       }
228     }
229     if (tileIterator.IsAtTileContainingPoint(rectOrigin)) {
230       pixman_box32_t rectIntersection = tileIterator.IntersectionWith(aRect);
231       if (aHandleNonEmptyTile(aRectArray, i, rectIntersection) ==
232           IterationAction::STOP) {
233         return IterationEndReason::STOPPED;
234       }
235       ++tileIterator;
236     }
237   }
238 
239   if (tileIterator != rangeEnd) {
240     // We've looked at all of our existing rectangles but haven't covered all
241     // of the tiles that we're interested in yet. So we need to deal with the
242     // remaining tiles now.
243     size_t endIndex = aRectArray.Length();
244     TileRange emptyTiles(tileBounds, *tileIterator, *rangeEnd);
245     if (aHandleEmptyTiles(aRectArray, endIndex, emptyTiles) ==
246         IterationAction::STOP) {
247       return IterationEndReason::STOPPED;
248     }
249   }
250   return IterationEndReason::NOT_STOPPED;
251 }
252 
UnionBoundsOfNonEmptyBoxes(const pixman_box32_t & aBox1,const pixman_box32_t & aBox2)253 static pixman_box32_t UnionBoundsOfNonEmptyBoxes(const pixman_box32_t& aBox1,
254                                                  const pixman_box32_t& aBox2) {
255   return {std::min(aBox1.x1, aBox2.x1), std::min(aBox1.y1, aBox2.y1),
256           std::max(aBox1.x2, aBox2.x2), std::max(aBox1.y2, aBox2.y2)};
257 }
258 
259 // Returns true when adding the rectangle was successful, and false if
260 // allocation failed.
261 // When this returns false, our internal state might not be consistent and we
262 // need to be cleared.
AddRect(const pixman_box32_t & aRect)263 bool TiledRegionImpl::AddRect(const pixman_box32_t& aRect) {
264   // We are adding a rectangle that can span multiple tiles.
265   // For each empty tile that aRect intersects, we need to add the intersection
266   // of aRect with that tile to mRects, respecting the order of mRects.
267   // For each tile that already has a rectangle, we need to enlarge that
268   // existing rectangle to include the intersection of aRect with the tile.
269   return ProcessIntersectedTiles(
270              aRect, mRects,
271              [&aRect](nsTArray<pixman_box32_t>& rects, size_t& rectIndex,
272                       TileRange emptyTiles) {
273                CheckedInt<size_t> newLength(rects.Length());
274                newLength += emptyTiles.Length();
275                if (!newLength.isValid() || newLength.value() >= kMaxTiles ||
276                    !rects.InsertElementsAt(rectIndex, emptyTiles.Length(),
277                                            fallible)) {
278                  return IterationAction::STOP;
279                }
280                for (TileIterator tileIt = emptyTiles.Begin();
281                     tileIt != emptyTiles.End(); ++tileIt, ++rectIndex) {
282                  rects[rectIndex] = tileIt.IntersectionWith(aRect);
283                }
284                return IterationAction::CONTINUE;
285              },
286              [](nsTArray<pixman_box32_t>& rects, size_t rectIndex,
287                 const pixman_box32_t& rectIntersectionWithTile) {
288                rects[rectIndex] = UnionBoundsOfNonEmptyBoxes(
289                    rects[rectIndex], rectIntersectionWithTile);
290                return IterationAction::CONTINUE;
291              }) == IterationEndReason::NOT_STOPPED;
292 }
293 
NonEmptyBoxesIntersect(const pixman_box32_t & aBox1,const pixman_box32_t & aBox2)294 static bool NonEmptyBoxesIntersect(const pixman_box32_t& aBox1,
295                                    const pixman_box32_t& aBox2) {
296   return aBox1.x1 < aBox2.x2 && aBox2.x1 < aBox1.x2 && aBox1.y1 < aBox2.y2 &&
297          aBox2.y1 < aBox1.y2;
298 }
299 
Intersects(const pixman_box32_t & aRect) const300 bool TiledRegionImpl::Intersects(const pixman_box32_t& aRect) const {
301   // aRect intersects this region if it intersects any of our rectangles.
302   return ProcessIntersectedTiles(
303              aRect, mRects,
304              [](const nsTArray<pixman_box32_t>& rects, size_t& rectIndex,
305                 TileRange emptyTiles) {
306                // Ignore empty tiles and keep on iterating.
307                return IterationAction::CONTINUE;
308              },
309              [](const nsTArray<pixman_box32_t>& rects, size_t rectIndex,
310                 const pixman_box32_t& rectIntersectionWithTile) {
311                if (NonEmptyBoxesIntersect(rects[rectIndex],
312                                           rectIntersectionWithTile)) {
313                  // Found an intersecting rectangle, so aRect intersects this
314                  // region.
315                  return IterationAction::STOP;
316                }
317                return IterationAction::CONTINUE;
318              }) == IterationEndReason::STOPPED;
319 }
320 
NonEmptyBoxContainsNonEmptyBox(const pixman_box32_t & aBox1,const pixman_box32_t & aBox2)321 static bool NonEmptyBoxContainsNonEmptyBox(const pixman_box32_t& aBox1,
322                                            const pixman_box32_t& aBox2) {
323   return aBox1.x1 <= aBox2.x1 && aBox2.x2 <= aBox1.x2 && aBox1.y1 <= aBox2.y1 &&
324          aBox2.y2 <= aBox1.y2;
325 }
326 
Contains(const pixman_box32_t & aRect) const327 bool TiledRegionImpl::Contains(const pixman_box32_t& aRect) const {
328   // aRect is contained in this region if aRect does not intersect any empty
329   // tiles and, for each non-empty tile, if the intersection of aRect with that
330   // tile is contained in the existing rectangle we have in that tile.
331   return ProcessIntersectedTiles(
332              aRect, mRects,
333              [](const nsTArray<pixman_box32_t>& rects, size_t& rectIndex,
334                 TileRange emptyTiles) {
335                // Found an empty tile that intersects aRect, so aRect is not
336                // contained in this region.
337                return IterationAction::STOP;
338              },
339              [](const nsTArray<pixman_box32_t>& rects, size_t rectIndex,
340                 const pixman_box32_t& rectIntersectionWithTile) {
341                if (!NonEmptyBoxContainsNonEmptyBox(rects[rectIndex],
342                                                    rectIntersectionWithTile)) {
343                  // Our existing rectangle in this tile does not cover the part
344                  // of aRect that intersects this tile, so aRect is not
345                  // contained in this region.
346                  return IterationAction::STOP;
347                }
348                return IterationAction::CONTINUE;
349              }) == IterationEndReason::NOT_STOPPED;
350 }
351 
352 }  // namespace gfx
353 }  // namespace mozilla
354