1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
3 * You can obtain one at http://mozilla.org/MPL/2.0/. */
5 #ifndef GFX_TILEDLAYERBUFFER_H
6 #define GFX_TILEDLAYERBUFFER_H
9 //#define GFX_TILEDLAYER_DEBUG_OVERLAY
10 //#define GFX_TILEDLAYER_PREF_WARNINGS
12 #include <stdint.h> // for uint16_t, uint32_t
13 #include <sys/types.h> // for int32_t
14 #include "gfxPrefs.h" // for gfxPrefs::LayersTileWidth/Height
15 #include "nsDebug.h" // for NS_ABORT_IF_FALSE
16 #include "nsPoint.h" // for nsIntPoint
17 #include "nsRect.h" // for nsIntRect
18 #include "nsRegion.h" // for nsIntRegion
19 #include "nsTArray.h" // for nsTArray
21 #if defined(MOZ_WIDGET_GONK) && ANDROID_VERSION >= 17
28 // You can enable all the TILING_LOG print statements by
29 // changing the 0 to a 1 in the following #define.
30 #define ENABLE_TILING_LOG 0
33 # define TILING_LOG(...) printf_stderr(__VA_ARGS__);
35 # define TILING_LOG(...)
38 // An abstract implementation of a tile buffer. This code covers the logic of
39 // moving and reusing tiles and leaves the validation up to the implementor. To
40 // avoid the overhead of virtual dispatch, we employ the curiously recurring
43 // Tiles are aligned to a grid with one of the grid points at (0,0) and other
44 // grid points spaced evenly in the x- and y-directions by GetTileSize()
45 // multiplied by mResolution. GetScaledTileSize() provides convenience for
46 // accessing these values.
48 // This tile buffer stores a valid region, which defines the areas that have
49 // up-to-date content. The contents of tiles within this region will be reused
50 // from paint to paint. It also stores the region that was modified in the last
51 // paint operation; this is useful when one tiled layer buffer shadows another
52 // (as in an off-main-thread-compositing scenario), so that the shadow tiled
53 // layer buffer can correctly reflect the updates of the master layer buffer.
55 // The associated Tile may be of any type as long as the derived class can
56 // validate and return tiles of that type. Tiles will be frequently copied, so
57 // the tile type should be a reference or some other type with an efficient
60 // It is required that the derived class specify the base class as a friend. It
61 // must also implement the following public method:
63 // Tile GetPlaceholderTile() const;
65 // Returns a temporary placeholder tile used as a marker. This placeholder tile
66 // must never be returned by validateTile and must be == to every instance
67 // of a placeholder tile.
69 // Additionally, it must implement the following protected methods:
71 // Tile ValidateTile(Tile aTile, const nsIntPoint& aTileOrigin,
72 // const nsIntRegion& aDirtyRect);
74 // Validates the dirtyRect. The returned Tile will replace the tile.
76 // void ReleaseTile(Tile aTile);
78 // Destroys the given tile.
80 // void SwapTiles(Tile& aTileA, Tile& aTileB);
84 // The contents of the tile buffer will be rendered at the resolution specified
85 // in mResolution, which can be altered with SetResolution. The resolution
86 // should always be a factor of the tile length, to avoid tiles covering
87 // non-integer amounts of pixels.
89 template<typename Derived
, typename Tile
>
90 class TiledLayerBuffer
97 , mTileSize(gfxPrefs::LayersTileWidth(), gfxPrefs::LayersTileHeight())
100 ~TiledLayerBuffer() {}
102 // Given a tile origin aligned to a multiple of GetScaledTileSize,
103 // return the tile that describes that region.
104 // NOTE: To get the valid area of that tile you must intersect
105 // (aTileOrigin.x, aTileOrigin.y,
106 // GetScaledTileSize().width, GetScaledTileSize().height)
107 // and GetValidRegion() to get the area of the tile that is valid.
108 Tile
GetTile(const nsIntPoint
& aTileOrigin
) const;
110 // Given a tile x, y relative to the top left of the layer, this function
111 // will return the tile for
112 // (x*GetScaledTileSize().width, y*GetScaledTileSize().height,
113 // GetScaledTileSize().width, GetScaledTileSize().height)
114 Tile
GetTile(int x
, int y
) const;
116 // This operates the same as GetTile(aTileOrigin), but will also replace the
117 // specified tile with the placeholder tile. This does not call ReleaseTile
118 // on the removed tile.
119 bool RemoveTile(const nsIntPoint
& aTileOrigin
, Tile
& aRemovedTile
);
121 // This operates the same as GetTile(x, y), but will also replace the
122 // specified tile with the placeholder tile. This does not call ReleaseTile
123 // on the removed tile.
124 bool RemoveTile(int x
, int y
, Tile
& aRemovedTile
);
126 const gfx::IntSize
& GetTileSize() const { return mTileSize
; }
128 gfx::IntSize
GetScaledTileSize() const { return RoundedToInt(gfx::Size(mTileSize
) / mResolution
); }
130 unsigned int GetTileCount() const { return mRetainedTiles
.Length(); }
132 const nsIntRegion
& GetValidRegion() const { return mValidRegion
; }
133 const nsIntRegion
& GetPaintedRegion() const { return mPaintedRegion
; }
134 void ClearPaintedRegion() { mPaintedRegion
.SetEmpty(); }
136 void ResetPaintedAndValidState() {
137 mPaintedRegion
.SetEmpty();
138 mValidRegion
.SetEmpty();
141 for (size_t i
= 0; i
< mRetainedTiles
.Length(); i
++) {
142 if (!IsPlaceholder(mRetainedTiles
[i
])) {
143 AsDerived().ReleaseTile(mRetainedTiles
[i
]);
146 mRetainedTiles
.Clear();
149 // Given a position i, this function returns the position inside the current tile.
150 int GetTileStart(int i
, int aTileLength
) const {
151 return (i
>= 0) ? (i
% aTileLength
)
152 : ((aTileLength
- (-i
% aTileLength
)) %
156 // Rounds the given coordinate down to the nearest tile boundary.
157 int RoundDownToTileEdge(int aX
, int aTileLength
) const { return aX
- GetTileStart(aX
, aTileLength
); }
159 // Get and set draw scaling. mResolution affects the resolution at which the
160 // contents of the buffer are drawn. mResolution has no effect on the
161 // coordinate space of the valid region, but does affect the size of an
162 // individual tile's rect in relation to the valid region.
163 // Setting the resolution will invalidate the buffer.
164 float GetResolution() const { return mResolution
; }
165 void SetResolution(float aResolution
) {
166 if (mResolution
== aResolution
) {
170 Update(nsIntRegion(), nsIntRegion());
171 mResolution
= aResolution
;
173 bool IsLowPrecision() const { return mResolution
< 1; }
175 typedef Tile
* Iterator
;
176 Iterator
TilesBegin() { return mRetainedTiles
.Elements(); }
177 Iterator
TilesEnd() { return mRetainedTiles
.Elements() + mRetainedTiles
.Length(); }
180 // The implementor should call Update() to change
181 // the new valid region. This implementation will call
182 // validateTile on each tile that is dirty, which is left
183 // to the implementor.
184 void Update(const nsIntRegion
& aNewValidRegion
, const nsIntRegion
& aPaintRegion
);
186 nsIntRegion mValidRegion
;
187 nsIntRegion mPaintedRegion
;
190 * mRetainedTiles is a rectangular buffer of mRetainedWidth x mRetainedHeight
191 * stored as column major with the same origin as mValidRegion.GetBounds().
192 * Any tile that does not intersect mValidRegion is a PlaceholderTile.
193 * Only the region intersecting with mValidRegion should be read from a tile,
194 * another other region is assumed to be uninitialized. The contents of the
195 * tiles is scaled by mResolution.
197 nsTArray
<Tile
> mRetainedTiles
;
198 int mRetainedWidth
; // in tiles
199 int mRetainedHeight
; // in tiles
201 gfx::IntSize mTileSize
;
204 const Derived
& AsDerived() const { return *static_cast<const Derived
*>(this); }
205 Derived
& AsDerived() { return *static_cast<Derived
*>(this); }
207 bool IsPlaceholder(Tile aTile
) const { return aTile
== AsDerived().GetPlaceholderTile(); }
210 class ClientTiledLayerBuffer
;
211 class SurfaceDescriptorTiles
;
212 class ISurfaceAllocator
;
214 // Shadow layers may implement this interface in order to be notified when a
215 // tiled layer buffer is updated.
216 class TiledLayerComposer
220 * Update the current retained layer with the updated layer data.
221 * It is expected that the tiles described by aTiledDescriptor are all in the
222 * ReadLock state, so that the locks can be adopted when recreating a
223 * ClientTiledLayerBuffer locally. This lock will be retained until the buffer
224 * has completed uploading.
226 * Returns false if a deserialization error happened, in which case we will
227 * have to kill the child process.
229 virtual bool UseTiledLayerBuffer(ISurfaceAllocator
* aAllocator
,
230 const SurfaceDescriptorTiles
& aTiledDescriptor
) = 0;
233 * If some part of the buffer is being rendered at a lower precision, this
234 * returns that region. If it is not, an empty region will be returned.
236 virtual const nsIntRegion
& GetValidLowPrecisionRegion() const = 0;
238 #if defined(MOZ_WIDGET_GONK) && ANDROID_VERSION >= 17
240 * Store a fence that will signal when the current buffer is no longer being read.
241 * Similar to android's GLConsumer::setReleaseFence()
243 virtual void SetReleaseFence(const android::sp
<android::Fence
>& aReleaseFence
) = 0;
247 // Normal integer division truncates towards zero,
248 // we instead want to floor to hangle negative numbers.
249 static inline int floor_div(int a
, int b
)
256 // If the signs are different substract 1.
259 // The results of this shift is either 0 or -1.
260 sub
>>= 8*sizeof(int)-1;
265 template<typename Derived
, typename Tile
> Tile
266 TiledLayerBuffer
<Derived
, Tile
>::GetTile(const nsIntPoint
& aTileOrigin
) const
268 // TODO Cache firstTileOriginX/firstTileOriginY
269 // Find the tile x/y of the first tile and the target tile relative to the (0, 0)
270 // origin, the difference is the tile x/y relative to the start of the tile buffer.
271 gfx::IntSize scaledTileSize
= GetScaledTileSize();
272 int firstTileX
= floor_div(mValidRegion
.GetBounds().x
, scaledTileSize
.width
);
273 int firstTileY
= floor_div(mValidRegion
.GetBounds().y
, scaledTileSize
.height
);
274 return GetTile(floor_div(aTileOrigin
.x
, scaledTileSize
.width
) - firstTileX
,
275 floor_div(aTileOrigin
.y
, scaledTileSize
.height
) - firstTileY
);
278 template<typename Derived
, typename Tile
> Tile
279 TiledLayerBuffer
<Derived
, Tile
>::GetTile(int x
, int y
) const
281 int index
= x
* mRetainedHeight
+ y
;
282 return mRetainedTiles
.SafeElementAt(index
, AsDerived().GetPlaceholderTile());
285 template<typename Derived
, typename Tile
> bool
286 TiledLayerBuffer
<Derived
, Tile
>::RemoveTile(const nsIntPoint
& aTileOrigin
,
289 gfx::IntSize scaledTileSize
= GetScaledTileSize();
290 int firstTileX
= floor_div(mValidRegion
.GetBounds().x
, scaledTileSize
.width
);
291 int firstTileY
= floor_div(mValidRegion
.GetBounds().y
, scaledTileSize
.height
);
292 return RemoveTile(floor_div(aTileOrigin
.x
, scaledTileSize
.width
) - firstTileX
,
293 floor_div(aTileOrigin
.y
, scaledTileSize
.height
) - firstTileY
,
297 template<typename Derived
, typename Tile
> bool
298 TiledLayerBuffer
<Derived
, Tile
>::RemoveTile(int x
, int y
, Tile
& aRemovedTile
)
300 int index
= x
* mRetainedHeight
+ y
;
301 const Tile
& tileToRemove
= mRetainedTiles
.SafeElementAt(index
, AsDerived().GetPlaceholderTile());
302 if (!IsPlaceholder(tileToRemove
)) {
303 aRemovedTile
= tileToRemove
;
304 mRetainedTiles
[index
] = AsDerived().GetPlaceholderTile();
310 template<typename Derived
, typename Tile
> void
311 TiledLayerBuffer
<Derived
, Tile
>::Update(const nsIntRegion
& aNewValidRegion
,
312 const nsIntRegion
& aPaintRegion
)
314 gfx::IntSize scaledTileSize
= GetScaledTileSize();
316 nsTArray
<Tile
> newRetainedTiles
;
317 nsTArray
<Tile
>& oldRetainedTiles
= mRetainedTiles
;
318 const nsIntRect oldBound
= mValidRegion
.GetBounds();
319 const nsIntRect newBound
= aNewValidRegion
.GetBounds();
320 const nsIntPoint
oldBufferOrigin(RoundDownToTileEdge(oldBound
.x
, scaledTileSize
.width
),
321 RoundDownToTileEdge(oldBound
.y
, scaledTileSize
.height
));
322 const nsIntPoint
newBufferOrigin(RoundDownToTileEdge(newBound
.x
, scaledTileSize
.width
),
323 RoundDownToTileEdge(newBound
.y
, scaledTileSize
.height
));
324 const nsIntRegion
& oldValidRegion
= mValidRegion
;
325 const nsIntRegion
& newValidRegion
= aNewValidRegion
;
326 const int oldRetainedHeight
= mRetainedHeight
;
328 // Pass 1: Recycle valid content from the old buffer
329 // Recycle tiles from the old buffer that contain valid regions.
330 // Insert placeholders tiles if we have no valid area for that tile
331 // which we will allocate in pass 2.
332 // TODO: Add a tile pool to reduce new allocation
335 int tilesMissing
= 0;
336 // Iterate over the new drawing bounds in steps of tiles.
337 for (int32_t x
= newBound
.x
; x
< newBound
.XMost(); tileX
++) {
338 // Compute tileRect(x,y,width,height) in layer space coordinate
339 // giving us the rect of the tile that hits the newBounds.
340 int width
= scaledTileSize
.width
- GetTileStart(x
, scaledTileSize
.width
);
341 if (x
+ width
> newBound
.XMost()) {
342 width
= newBound
.x
+ newBound
.width
- x
;
346 for (int32_t y
= newBound
.y
; y
< newBound
.YMost(); tileY
++) {
347 int height
= scaledTileSize
.height
- GetTileStart(y
, scaledTileSize
.height
);
348 if (y
+ height
> newBound
.y
+ newBound
.height
) {
349 height
= newBound
.y
+ newBound
.height
- y
;
352 const nsIntRect
tileRect(x
,y
,width
,height
);
353 if (oldValidRegion
.Intersects(tileRect
) && newValidRegion
.Intersects(tileRect
)) {
354 // This old tiles contains some valid area so move it to the new tile
355 // buffer. Replace the tile in the old buffer with a placeholder
356 // to leave the old buffer index unaffected.
357 int tileX
= floor_div(x
- oldBufferOrigin
.x
, scaledTileSize
.width
);
358 int tileY
= floor_div(y
- oldBufferOrigin
.y
, scaledTileSize
.height
);
359 int index
= tileX
* oldRetainedHeight
+ tileY
;
361 // The tile may have been removed, skip over it in this case.
362 if (IsPlaceholder(oldRetainedTiles
.
363 SafeElementAt(index
, AsDerived().GetPlaceholderTile()))) {
364 newRetainedTiles
.AppendElement(AsDerived().GetPlaceholderTile());
366 Tile tileWithPartialValidContent
= oldRetainedTiles
[index
];
367 newRetainedTiles
.AppendElement(tileWithPartialValidContent
);
368 oldRetainedTiles
[index
] = AsDerived().GetPlaceholderTile();
372 // This tile is either:
373 // 1) Outside the new valid region and will simply be an empty
374 // placeholder forever.
375 // 2) The old buffer didn't have any data for this tile. We postpone
376 // the allocation of this tile after we've reused any tile with
377 // valid content because then we know we can safely recycle
378 // with taking from a tile that has recyclable content.
379 newRetainedTiles
.AppendElement(AsDerived().GetPlaceholderTile());
381 if (aPaintRegion
.Intersects(tileRect
)) {
392 // Keep track of the number of horizontal/vertical tiles
393 // in the buffer so that we can easily look up a tile.
394 mRetainedWidth
= tileX
;
395 mRetainedHeight
= tileY
;
397 // Pass 1.5: Release excess tiles in oldRetainedTiles
398 // Tiles in oldRetainedTiles that aren't in newRetainedTiles will be recycled
399 // before creating new ones, but there could still be excess unnecessary
400 // tiles. As tiles may not have a fixed memory cost (for example, due to
401 // double-buffering), we should release these excess tiles first.
402 int oldTileCount
= 0;
403 for (size_t i
= 0; i
< oldRetainedTiles
.Length(); i
++) {
404 Tile oldTile
= oldRetainedTiles
[i
];
405 if (IsPlaceholder(oldTile
)) {
409 if (oldTileCount
>= tilesMissing
) {
410 oldRetainedTiles
[i
] = AsDerived().GetPlaceholderTile();
411 AsDerived().ReleaseTile(oldTile
);
417 NS_ABORT_IF_FALSE(aNewValidRegion
.Contains(aPaintRegion
), "Painting a region outside the visible region");
419 nsIntRegion
oldAndPainted(oldValidRegion
);
420 oldAndPainted
.Or(oldAndPainted
, aPaintRegion
);
422 NS_ABORT_IF_FALSE(oldAndPainted
.Contains(newValidRegion
), "newValidRegion has not been fully painted");
424 nsIntRegion
regionToPaint(aPaintRegion
);
427 // We know at this point that any tile in the new buffer that had valid content
428 // from the previous buffer is placed correctly in the new buffer.
429 // We know that any tile in the old buffer that isn't a place holder is
430 // of no use and can be recycled.
431 // We also know that any place holder tile in the new buffer must be
434 #ifdef GFX_TILEDLAYER_PREF_WARNINGS
435 printf_stderr("Update %i, %i, %i, %i\n", newBound
.x
, newBound
.y
, newBound
.width
, newBound
.height
);
437 for (int x
= newBound
.x
; x
< newBound
.x
+ newBound
.width
; tileX
++) {
438 // Compute tileRect(x,y,width,height) in layer space coordinate
439 // giving us the rect of the tile that hits the newBounds.
440 int tileStartX
= RoundDownToTileEdge(x
, scaledTileSize
.width
);
441 int width
= scaledTileSize
.width
- GetTileStart(x
, scaledTileSize
.width
);
442 if (x
+ width
> newBound
.XMost())
443 width
= newBound
.XMost() - x
;
446 for (int y
= newBound
.y
; y
< newBound
.y
+ newBound
.height
; tileY
++) {
447 int tileStartY
= RoundDownToTileEdge(y
, scaledTileSize
.height
);
448 int height
= scaledTileSize
.height
- GetTileStart(y
, scaledTileSize
.height
);
449 if (y
+ height
> newBound
.YMost()) {
450 height
= newBound
.YMost() - y
;
453 const nsIntRect
tileRect(x
, y
, width
, height
);
455 nsIntRegion tileDrawRegion
;
456 tileDrawRegion
.And(tileRect
, regionToPaint
);
458 if (tileDrawRegion
.IsEmpty()) {
459 // We have a tile but it doesn't hit the draw region
460 // because we can reuse all of the content from the
463 int currTileX
= floor_div(x
- newBufferOrigin
.x
, scaledTileSize
.width
);
464 int currTileY
= floor_div(y
- newBufferOrigin
.y
, scaledTileSize
.height
);
465 int index
= currTileX
* mRetainedHeight
+ currTileY
;
466 // If allocating a tile failed we can run into this assertion.
467 // Rendering is going to be glitchy but we don't want to crash.
468 NS_ASSERTION(!newValidRegion
.Intersects(tileRect
) ||
469 !IsPlaceholder(newRetainedTiles
.
470 SafeElementAt(index
, AsDerived().GetPlaceholderTile())),
471 "Unexpected placeholder tile");
478 int tileX
= floor_div(x
- newBufferOrigin
.x
, scaledTileSize
.width
);
479 int tileY
= floor_div(y
- newBufferOrigin
.y
, scaledTileSize
.height
);
480 int index
= tileX
* mRetainedHeight
+ tileY
;
481 NS_ABORT_IF_FALSE(index
>= 0 &&
482 static_cast<unsigned>(index
) < newRetainedTiles
.Length(),
483 "index out of range");
485 Tile newTile
= newRetainedTiles
[index
];
487 // Try to reuse a tile from the old retained tiles that had no partially
489 while (IsPlaceholder(newTile
) && oldRetainedTiles
.Length() > 0) {
490 AsDerived().SwapTiles(newTile
, oldRetainedTiles
[oldRetainedTiles
.Length()-1]);
491 oldRetainedTiles
.RemoveElementAt(oldRetainedTiles
.Length()-1);
492 if (!IsPlaceholder(newTile
)) {
497 // We've done our best effort to recycle a tile but it can be null
498 // in which case it's up to the derived class's ValidateTile()
499 // implementation to allocate a new tile before drawing
500 nsIntPoint
tileOrigin(tileStartX
, tileStartY
);
501 newTile
= AsDerived().ValidateTile(newTile
, nsIntPoint(tileStartX
, tileStartY
),
503 NS_ASSERTION(!IsPlaceholder(newTile
), "Unexpected placeholder tile - failed to allocate?");
504 #ifdef GFX_TILEDLAYER_PREF_WARNINGS
505 printf_stderr("Store Validate tile %i, %i -> %i\n", tileStartX
, tileStartY
, index
);
507 newRetainedTiles
[index
] = newTile
;
515 AsDerived().PostValidate(aPaintRegion
);
516 for (unsigned int i
= 0; i
< newRetainedTiles
.Length(); ++i
) {
517 AsDerived().UnlockTile(newRetainedTiles
[i
]);
520 // At this point, oldTileCount should be zero
521 NS_ABORT_IF_FALSE(oldTileCount
== 0, "Failed to release old tiles");
523 mRetainedTiles
= newRetainedTiles
;
524 mValidRegion
= aNewValidRegion
;
525 mPaintedRegion
.Or(mPaintedRegion
, aPaintRegion
);
531 #endif // GFX_TILEDLAYERBUFFER_H