Bumping manifests a=b2g-bump
[gecko.git] / gfx / layers / TiledLayerBuffer.h
blob49ce4ed2bd576e0bd4d31677d04d7822df0bf83b
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
8 // Debug defines
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 "gfxPlatform.h" // for GetTileWidth/GetTileHeight
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
22 #include <ui/Fence.h>
23 #endif
25 namespace mozilla {
26 namespace layers {
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
32 #if ENABLE_TILING_LOG
33 # define TILING_LOG(...) printf_stderr(__VA_ARGS__);
34 #else
35 # define TILING_LOG(...)
36 #endif
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
41 // template pattern.
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
58 // copy constructor.
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);
82 // Swaps two tiles.
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
92 public:
93 TiledLayerBuffer()
94 : mRetainedWidth(0)
95 , mRetainedHeight(0)
96 , mResolution(1)
97 , mTileSize(gfxPlatform::GetPlatform()->GetTileWidth(), gfxPlatform::GetPlatform()->GetTileHeight())
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();
139 mRetainedWidth = 0;
140 mRetainedHeight = 0;
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)) %
153 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) {
167 return;
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(); }
179 void Dump(std::stringstream& aStream, const char* aPrefix, bool aDumpHtml);
181 protected:
182 // The implementor should call Update() to change
183 // the new valid region. This implementation will call
184 // validateTile on each tile that is dirty, which is left
185 // to the implementor.
186 void Update(const nsIntRegion& aNewValidRegion, const nsIntRegion& aPaintRegion);
188 nsIntRegion mValidRegion;
189 nsIntRegion mPaintedRegion;
192 * mRetainedTiles is a rectangular buffer of mRetainedWidth x mRetainedHeight
193 * stored as column major with the same origin as mValidRegion.GetBounds().
194 * Any tile that does not intersect mValidRegion is a PlaceholderTile.
195 * Only the region intersecting with mValidRegion should be read from a tile,
196 * another other region is assumed to be uninitialized. The contents of the
197 * tiles is scaled by mResolution.
199 nsTArray<Tile> mRetainedTiles;
200 int mRetainedWidth; // in tiles
201 int mRetainedHeight; // in tiles
202 float mResolution;
203 gfx::IntSize mTileSize;
205 private:
206 const Derived& AsDerived() const { return *static_cast<const Derived*>(this); }
207 Derived& AsDerived() { return *static_cast<Derived*>(this); }
209 bool IsPlaceholder(Tile aTile) const { return aTile == AsDerived().GetPlaceholderTile(); }
212 class ClientTiledLayerBuffer;
213 class SurfaceDescriptorTiles;
214 class ISurfaceAllocator;
216 // Shadow layers may implement this interface in order to be notified when a
217 // tiled layer buffer is updated.
218 class TiledLayerComposer
220 public:
222 * Update the current retained layer with the updated layer data.
223 * It is expected that the tiles described by aTiledDescriptor are all in the
224 * ReadLock state, so that the locks can be adopted when recreating a
225 * ClientTiledLayerBuffer locally. This lock will be retained until the buffer
226 * has completed uploading.
228 * Returns false if a deserialization error happened, in which case we will
229 * have to kill the child process.
231 virtual bool UseTiledLayerBuffer(ISurfaceAllocator* aAllocator,
232 const SurfaceDescriptorTiles& aTiledDescriptor) = 0;
235 * If some part of the buffer is being rendered at a lower precision, this
236 * returns that region. If it is not, an empty region will be returned.
238 virtual const nsIntRegion& GetValidLowPrecisionRegion() const = 0;
240 virtual const nsIntRegion& GetValidRegion() const = 0;
242 #if defined(MOZ_WIDGET_GONK) && ANDROID_VERSION >= 17
244 * Store a fence that will signal when the current buffer is no longer being read.
245 * Similar to android's GLConsumer::setReleaseFence()
247 virtual void SetReleaseFence(const android::sp<android::Fence>& aReleaseFence) = 0;
248 #endif
251 // Normal integer division truncates towards zero,
252 // we instead want to floor to hangle negative numbers.
253 static inline int floor_div(int a, int b)
255 int rem = a % b;
256 int div = a/b;
257 if (rem == 0) {
258 return div;
259 } else {
260 // If the signs are different substract 1.
261 int sub;
262 sub = a ^ b;
263 // The results of this shift is either 0 or -1.
264 sub >>= 8*sizeof(int)-1;
265 return div+sub;
269 template<typename Derived, typename Tile> Tile
270 TiledLayerBuffer<Derived, Tile>::GetTile(const nsIntPoint& aTileOrigin) const
272 // TODO Cache firstTileOriginX/firstTileOriginY
273 // Find the tile x/y of the first tile and the target tile relative to the (0, 0)
274 // origin, the difference is the tile x/y relative to the start of the tile buffer.
275 gfx::IntSize scaledTileSize = GetScaledTileSize();
276 int firstTileX = floor_div(mValidRegion.GetBounds().x, scaledTileSize.width);
277 int firstTileY = floor_div(mValidRegion.GetBounds().y, scaledTileSize.height);
278 return GetTile(floor_div(aTileOrigin.x, scaledTileSize.width) - firstTileX,
279 floor_div(aTileOrigin.y, scaledTileSize.height) - firstTileY);
282 template<typename Derived, typename Tile> Tile
283 TiledLayerBuffer<Derived, Tile>::GetTile(int x, int y) const
285 int index = x * mRetainedHeight + y;
286 return mRetainedTiles.SafeElementAt(index, AsDerived().GetPlaceholderTile());
289 template<typename Derived, typename Tile> bool
290 TiledLayerBuffer<Derived, Tile>::RemoveTile(const nsIntPoint& aTileOrigin,
291 Tile& aRemovedTile)
293 gfx::IntSize scaledTileSize = GetScaledTileSize();
294 int firstTileX = floor_div(mValidRegion.GetBounds().x, scaledTileSize.width);
295 int firstTileY = floor_div(mValidRegion.GetBounds().y, scaledTileSize.height);
296 return RemoveTile(floor_div(aTileOrigin.x, scaledTileSize.width) - firstTileX,
297 floor_div(aTileOrigin.y, scaledTileSize.height) - firstTileY,
298 aRemovedTile);
301 template<typename Derived, typename Tile> bool
302 TiledLayerBuffer<Derived, Tile>::RemoveTile(int x, int y, Tile& aRemovedTile)
304 int index = x * mRetainedHeight + y;
305 const Tile& tileToRemove = mRetainedTiles.SafeElementAt(index, AsDerived().GetPlaceholderTile());
306 if (!IsPlaceholder(tileToRemove)) {
307 aRemovedTile = tileToRemove;
308 mRetainedTiles[index] = AsDerived().GetPlaceholderTile();
309 return true;
311 return false;
314 template<typename Derived, typename Tile> void
315 TiledLayerBuffer<Derived, Tile>::Dump(std::stringstream& aStream,
316 const char* aPrefix,
317 bool aDumpHtml)
319 nsIntRect visibleRect = GetValidRegion().GetBounds();
320 gfx::IntSize scaledTileSize = GetScaledTileSize();
321 for (int32_t x = visibleRect.x; x < visibleRect.x + visibleRect.width;) {
322 int32_t tileStartX = GetTileStart(x, scaledTileSize.width);
323 int32_t w = scaledTileSize.width - tileStartX;
325 for (int32_t y = visibleRect.y; y < visibleRect.y + visibleRect.height;) {
326 int32_t tileStartY = GetTileStart(y, scaledTileSize.height);
327 Tile tileTexture =
328 GetTile(nsIntPoint(RoundDownToTileEdge(x, scaledTileSize.width),
329 RoundDownToTileEdge(y, scaledTileSize.height)));
330 int32_t h = scaledTileSize.height - tileStartY;
332 aStream << "\n" << aPrefix << "Tile (x=" <<
333 RoundDownToTileEdge(x, scaledTileSize.width) << ", y=" <<
334 RoundDownToTileEdge(y, scaledTileSize.height) << "): ";
335 if (tileTexture != AsDerived().GetPlaceholderTile()) {
336 tileTexture.DumpTexture(aStream);
337 } else {
338 aStream << "empty tile";
340 y += h;
342 x += w;
346 template<typename Derived, typename Tile> void
347 TiledLayerBuffer<Derived, Tile>::Update(const nsIntRegion& aNewValidRegion,
348 const nsIntRegion& aPaintRegion)
350 gfx::IntSize scaledTileSize = GetScaledTileSize();
352 nsTArray<Tile> newRetainedTiles;
353 nsTArray<Tile>& oldRetainedTiles = mRetainedTiles;
354 const nsIntRect oldBound = mValidRegion.GetBounds();
355 const nsIntRect newBound = aNewValidRegion.GetBounds();
356 const nsIntPoint oldBufferOrigin(RoundDownToTileEdge(oldBound.x, scaledTileSize.width),
357 RoundDownToTileEdge(oldBound.y, scaledTileSize.height));
358 const nsIntPoint newBufferOrigin(RoundDownToTileEdge(newBound.x, scaledTileSize.width),
359 RoundDownToTileEdge(newBound.y, scaledTileSize.height));
360 const nsIntRegion& oldValidRegion = mValidRegion;
361 const nsIntRegion& newValidRegion = aNewValidRegion;
362 const int oldRetainedHeight = mRetainedHeight;
364 // Pass 1: Recycle valid content from the old buffer
365 // Recycle tiles from the old buffer that contain valid regions.
366 // Insert placeholders tiles if we have no valid area for that tile
367 // which we will allocate in pass 2.
368 // TODO: Add a tile pool to reduce new allocation
369 int tileX = 0;
370 int tileY = 0;
371 int tilesMissing = 0;
372 // Iterate over the new drawing bounds in steps of tiles.
373 for (int32_t x = newBound.x; x < newBound.XMost(); tileX++) {
374 // Compute tileRect(x,y,width,height) in layer space coordinate
375 // giving us the rect of the tile that hits the newBounds.
376 int width = scaledTileSize.width - GetTileStart(x, scaledTileSize.width);
377 if (x + width > newBound.XMost()) {
378 width = newBound.x + newBound.width - x;
381 tileY = 0;
382 for (int32_t y = newBound.y; y < newBound.YMost(); tileY++) {
383 int height = scaledTileSize.height - GetTileStart(y, scaledTileSize.height);
384 if (y + height > newBound.y + newBound.height) {
385 height = newBound.y + newBound.height - y;
388 const nsIntRect tileRect(x,y,width,height);
389 if (oldValidRegion.Intersects(tileRect) && newValidRegion.Intersects(tileRect)) {
390 // This old tiles contains some valid area so move it to the new tile
391 // buffer. Replace the tile in the old buffer with a placeholder
392 // to leave the old buffer index unaffected.
393 int tileX = floor_div(x - oldBufferOrigin.x, scaledTileSize.width);
394 int tileY = floor_div(y - oldBufferOrigin.y, scaledTileSize.height);
395 int index = tileX * oldRetainedHeight + tileY;
397 // The tile may have been removed, skip over it in this case.
398 if (IsPlaceholder(oldRetainedTiles.
399 SafeElementAt(index, AsDerived().GetPlaceholderTile()))) {
400 newRetainedTiles.AppendElement(AsDerived().GetPlaceholderTile());
401 } else {
402 Tile tileWithPartialValidContent = oldRetainedTiles[index];
403 newRetainedTiles.AppendElement(tileWithPartialValidContent);
404 oldRetainedTiles[index] = AsDerived().GetPlaceholderTile();
407 } else {
408 // This tile is either:
409 // 1) Outside the new valid region and will simply be an empty
410 // placeholder forever.
411 // 2) The old buffer didn't have any data for this tile. We postpone
412 // the allocation of this tile after we've reused any tile with
413 // valid content because then we know we can safely recycle
414 // with taking from a tile that has recyclable content.
415 newRetainedTiles.AppendElement(AsDerived().GetPlaceholderTile());
417 if (aPaintRegion.Intersects(tileRect)) {
418 tilesMissing++;
422 y += height;
425 x += width;
428 // Keep track of the number of horizontal/vertical tiles
429 // in the buffer so that we can easily look up a tile.
430 mRetainedWidth = tileX;
431 mRetainedHeight = tileY;
433 // Pass 1.5: Release excess tiles in oldRetainedTiles
434 // Tiles in oldRetainedTiles that aren't in newRetainedTiles will be recycled
435 // before creating new ones, but there could still be excess unnecessary
436 // tiles. As tiles may not have a fixed memory cost (for example, due to
437 // double-buffering), we should release these excess tiles first.
438 int oldTileCount = 0;
439 for (size_t i = 0; i < oldRetainedTiles.Length(); i++) {
440 Tile oldTile = oldRetainedTiles[i];
441 if (IsPlaceholder(oldTile)) {
442 continue;
445 if (oldTileCount >= tilesMissing) {
446 oldRetainedTiles[i] = AsDerived().GetPlaceholderTile();
447 AsDerived().ReleaseTile(oldTile);
448 } else {
449 oldTileCount ++;
453 NS_ABORT_IF_FALSE(aNewValidRegion.Contains(aPaintRegion), "Painting a region outside the visible region");
454 #ifdef DEBUG
455 nsIntRegion oldAndPainted(oldValidRegion);
456 oldAndPainted.Or(oldAndPainted, aPaintRegion);
457 #endif
458 NS_ABORT_IF_FALSE(oldAndPainted.Contains(newValidRegion), "newValidRegion has not been fully painted");
460 nsIntRegion regionToPaint(aPaintRegion);
462 // Pass 2: Validate
463 // We know at this point that any tile in the new buffer that had valid content
464 // from the previous buffer is placed correctly in the new buffer.
465 // We know that any tile in the old buffer that isn't a place holder is
466 // of no use and can be recycled.
467 // We also know that any place holder tile in the new buffer must be
468 // allocated.
469 tileX = 0;
470 #ifdef GFX_TILEDLAYER_PREF_WARNINGS
471 printf_stderr("Update %i, %i, %i, %i\n", newBound.x, newBound.y, newBound.width, newBound.height);
472 #endif
473 for (int x = newBound.x; x < newBound.x + newBound.width; tileX++) {
474 // Compute tileRect(x,y,width,height) in layer space coordinate
475 // giving us the rect of the tile that hits the newBounds.
476 int tileStartX = RoundDownToTileEdge(x, scaledTileSize.width);
477 int width = scaledTileSize.width - GetTileStart(x, scaledTileSize.width);
478 if (x + width > newBound.XMost())
479 width = newBound.XMost() - x;
481 tileY = 0;
482 for (int y = newBound.y; y < newBound.y + newBound.height; tileY++) {
483 int tileStartY = RoundDownToTileEdge(y, scaledTileSize.height);
484 int height = scaledTileSize.height - GetTileStart(y, scaledTileSize.height);
485 if (y + height > newBound.YMost()) {
486 height = newBound.YMost() - y;
489 const nsIntRect tileRect(x, y, width, height);
491 nsIntRegion tileDrawRegion;
492 tileDrawRegion.And(tileRect, regionToPaint);
494 if (tileDrawRegion.IsEmpty()) {
495 // We have a tile but it doesn't hit the draw region
496 // because we can reuse all of the content from the
497 // previous buffer.
498 #ifdef DEBUG
499 int currTileX = floor_div(x - newBufferOrigin.x, scaledTileSize.width);
500 int currTileY = floor_div(y - newBufferOrigin.y, scaledTileSize.height);
501 int index = currTileX * mRetainedHeight + currTileY;
502 // If allocating a tile failed we can run into this assertion.
503 // Rendering is going to be glitchy but we don't want to crash.
504 NS_ASSERTION(!newValidRegion.Intersects(tileRect) ||
505 !IsPlaceholder(newRetainedTiles.
506 SafeElementAt(index, AsDerived().GetPlaceholderTile())),
507 "Unexpected placeholder tile");
509 #endif
510 y += height;
511 continue;
514 int tileX = floor_div(x - newBufferOrigin.x, scaledTileSize.width);
515 int tileY = floor_div(y - newBufferOrigin.y, scaledTileSize.height);
516 int index = tileX * mRetainedHeight + tileY;
517 NS_ABORT_IF_FALSE(index >= 0 &&
518 static_cast<unsigned>(index) < newRetainedTiles.Length(),
519 "index out of range");
521 Tile newTile = newRetainedTiles[index];
523 // Try to reuse a tile from the old retained tiles that had no partially
524 // valid content.
525 while (IsPlaceholder(newTile) && oldRetainedTiles.Length() > 0) {
526 AsDerived().SwapTiles(newTile, oldRetainedTiles[oldRetainedTiles.Length()-1]);
527 oldRetainedTiles.RemoveElementAt(oldRetainedTiles.Length()-1);
528 if (!IsPlaceholder(newTile)) {
529 oldTileCount--;
533 // We've done our best effort to recycle a tile but it can be null
534 // in which case it's up to the derived class's ValidateTile()
535 // implementation to allocate a new tile before drawing
536 nsIntPoint tileOrigin(tileStartX, tileStartY);
537 newTile = AsDerived().ValidateTile(newTile, nsIntPoint(tileStartX, tileStartY),
538 tileDrawRegion);
539 NS_ASSERTION(!IsPlaceholder(newTile), "Unexpected placeholder tile - failed to allocate?");
540 #ifdef GFX_TILEDLAYER_PREF_WARNINGS
541 printf_stderr("Store Validate tile %i, %i -> %i\n", tileStartX, tileStartY, index);
542 #endif
543 newRetainedTiles[index] = newTile;
545 y += height;
548 x += width;
551 AsDerived().PostValidate(aPaintRegion);
552 for (unsigned int i = 0; i < newRetainedTiles.Length(); ++i) {
553 AsDerived().UnlockTile(newRetainedTiles[i]);
556 // At this point, oldTileCount should be zero
557 NS_ABORT_IF_FALSE(oldTileCount == 0, "Failed to release old tiles");
559 mRetainedTiles = newRetainedTiles;
560 mValidRegion = aNewValidRegion;
561 mPaintedRegion.Or(mPaintedRegion, aPaintRegion);
564 } // layers
565 } // mozilla
567 #endif // GFX_TILEDLAYERBUFFER_H