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/. */
7 #include "TiledRegion.h"
11 #include "mozilla/fallible.h"
16 static const int32_t kTileSize
= 256;
17 static const size_t kMaxTiles
= 1000;
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
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
)};
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.
48 TileIterator(const pixman_box32_t
& aTileBounds
, const IntPoint
& aPosition
)
49 : mTileBounds(aTileBounds
), mPos(aPosition
) {}
51 bool operator!=(const TileIterator
& aOther
) { return mPos
!= aOther
.mPos
; }
52 bool operator==(const TileIterator
& aOther
) { return mPos
== aOther
.mPos
; }
54 IntPoint
operator*() const { return mPos
; }
56 const TileIterator
& operator++() {
58 if (mPos
.x
>= mTileBounds
.x2
) {
59 mPos
.x
= mTileBounds
.x1
;
65 TileIterator
& operator=(const IntPoint
& aPosition
) {
70 bool IsBeforeTileContainingPoint(const IntPoint
& aPoint
) const {
71 return (mPos
.y
+ kTileSize
) <= aPoint
.y
||
72 (mPos
.y
<= aPoint
.y
&& (mPos
.x
+ kTileSize
) <= aPoint
.x
);
75 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
);
80 pixman_box32_t
IntersectionWith(const pixman_box32_t
& aRect
) const {
81 pixman_box32_t tile
= {mPos
.x
, mPos
.y
, mPos
.x
+ kTileSize
,
83 return IntersectionOfNonEmptyBoxes(tile
, aRect
);
87 const pixman_box32_t
& mTileBounds
;
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
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
100 // aTileBounds, aStart and aEnd need to be aligned with the tile grid.
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.
105 explicit TileRange(const pixman_box32_t
& aTileBounds
)
106 : mTileBounds(aTileBounds
),
107 mStart(mTileBounds
.x1
, mTileBounds
.y1
),
108 mEnd(mTileBounds
.x1
, mTileBounds
.y2
) {}
110 TileIterator
Begin() const { return TileIterator(mTileBounds
, mStart
); }
111 TileIterator
End() const { return TileIterator(mTileBounds
, mEnd
); }
113 // The number of tiles in this tile range.
114 size_t Length() const {
115 if (mEnd
.y
== mStart
.y
) {
116 return (mEnd
.x
- mStart
.x
) / kTileSize
;
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
;
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
);
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
143 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
;
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
;
164 const pixman_box32_t
& mTileBounds
;
165 const IntPoint mStart
;
169 static IntPoint
TileContainingPoint(const IntPoint
& aPoint
) {
170 return IntPoint(RoundDownToMultiple(aPoint
.x
, kTileSize
),
171 RoundDownToMultiple(aPoint
.y
, kTileSize
));
174 enum class IterationAction
: uint8_t { CONTINUE
, STOP
};
176 enum class IterationEndReason
: uint8_t { NOT_STOPPED
, STOPPED
};
178 template <typename HandleEmptyTilesFunction
,
179 typename HandleNonEmptyTileFunction
, typename RectArrayT
>
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
;
193 TileRange
tileRange(tileBounds
);
194 TileIterator rangeEnd
= tileRange
.End();
196 // tileIterator points to the next tile in tileRange, or to rangeEnd if we're
198 TileIterator tileIterator
= tileRange
.Begin();
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,
203 // - Zero if the rectangle we encountered is outside the tiles that
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
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
,
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
;
224 tileIterator
= afterEmptyTiles
;
225 if (tileIterator
== rangeEnd
) {
226 return IterationEndReason::NOT_STOPPED
;
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
;
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
;
250 return IterationEndReason::NOT_STOPPED
;
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
)};
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.
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(
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(),
278 return IterationAction::STOP
;
280 for (TileIterator tileIt
= emptyTiles
.Begin();
281 tileIt
!= emptyTiles
.End(); ++tileIt
, ++rectIndex
) {
282 rects
[rectIndex
] = tileIt
.IntersectionWith(aRect
);
284 return IterationAction::CONTINUE
;
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
;
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
&&
300 bool TiledRegionImpl::Intersects(const pixman_box32_t
& aRect
) const {
301 // aRect intersects this region if it intersects any of our rectangles.
302 return ProcessIntersectedTiles(
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
;
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
315 return IterationAction::STOP
;
317 return IterationAction::CONTINUE
;
318 }) == IterationEndReason::STOPPED
;
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
;
327 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(
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
;
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
;
348 return IterationAction::CONTINUE
;
349 }) == IterationEndReason::NOT_STOPPED
;
353 } // namespace mozilla