michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this file, michael@0: * You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #ifndef GFX_TILEDLAYERBUFFER_H michael@0: #define GFX_TILEDLAYERBUFFER_H michael@0: michael@0: // Debug defines michael@0: //#define GFX_TILEDLAYER_DEBUG_OVERLAY michael@0: //#define GFX_TILEDLAYER_PREF_WARNINGS michael@0: michael@0: #include // for uint16_t, uint32_t michael@0: #include // for int32_t michael@0: #include "gfxPrefs.h" // for gfxPrefs::LayersTileWidth/Height michael@0: #include "nsDebug.h" // for NS_ABORT_IF_FALSE michael@0: #include "nsPoint.h" // for nsIntPoint michael@0: #include "nsRect.h" // for nsIntRect michael@0: #include "nsRegion.h" // for nsIntRegion michael@0: #include "nsTArray.h" // for nsTArray michael@0: michael@0: #if defined(MOZ_WIDGET_GONK) && ANDROID_VERSION >= 17 michael@0: #include michael@0: #endif michael@0: michael@0: namespace mozilla { michael@0: namespace layers { michael@0: michael@0: // An abstract implementation of a tile buffer. This code covers the logic of michael@0: // moving and reusing tiles and leaves the validation up to the implementor. To michael@0: // avoid the overhead of virtual dispatch, we employ the curiously recurring michael@0: // template pattern. michael@0: // michael@0: // Tiles are aligned to a grid with one of the grid points at (0,0) and other michael@0: // grid points spaced evenly in the x- and y-directions by GetTileSize() michael@0: // multiplied by mResolution. GetScaledTileSize() provides convenience for michael@0: // accessing these values. michael@0: // michael@0: // This tile buffer stores a valid region, which defines the areas that have michael@0: // up-to-date content. The contents of tiles within this region will be reused michael@0: // from paint to paint. It also stores the region that was modified in the last michael@0: // paint operation; this is useful when one tiled layer buffer shadows another michael@0: // (as in an off-main-thread-compositing scenario), so that the shadow tiled michael@0: // layer buffer can correctly reflect the updates of the master layer buffer. michael@0: // michael@0: // The associated Tile may be of any type as long as the derived class can michael@0: // validate and return tiles of that type. Tiles will be frequently copied, so michael@0: // the tile type should be a reference or some other type with an efficient michael@0: // copy constructor. michael@0: // michael@0: // It is required that the derived class specify the base class as a friend. It michael@0: // must also implement the following public method: michael@0: // michael@0: // Tile GetPlaceholderTile() const; michael@0: // michael@0: // Returns a temporary placeholder tile used as a marker. This placeholder tile michael@0: // must never be returned by validateTile and must be == to every instance michael@0: // of a placeholder tile. michael@0: // michael@0: // Additionally, it must implement the following protected methods: michael@0: // michael@0: // Tile ValidateTile(Tile aTile, const nsIntPoint& aTileOrigin, michael@0: // const nsIntRegion& aDirtyRect); michael@0: // michael@0: // Validates the dirtyRect. The returned Tile will replace the tile. michael@0: // michael@0: // void ReleaseTile(Tile aTile); michael@0: // michael@0: // Destroys the given tile. michael@0: // michael@0: // void SwapTiles(Tile& aTileA, Tile& aTileB); michael@0: // michael@0: // Swaps two tiles. michael@0: // michael@0: // The contents of the tile buffer will be rendered at the resolution specified michael@0: // in mResolution, which can be altered with SetResolution. The resolution michael@0: // should always be a factor of the tile length, to avoid tiles covering michael@0: // non-integer amounts of pixels. michael@0: michael@0: template michael@0: class TiledLayerBuffer michael@0: { michael@0: public: michael@0: TiledLayerBuffer() michael@0: : mRetainedWidth(0) michael@0: , mRetainedHeight(0) michael@0: , mResolution(1) michael@0: , mTileSize(gfxPrefs::LayersTileWidth(), gfxPrefs::LayersTileHeight()) michael@0: {} michael@0: michael@0: ~TiledLayerBuffer() {} michael@0: michael@0: // Given a tile origin aligned to a multiple of GetScaledTileSize, michael@0: // return the tile that describes that region. michael@0: // NOTE: To get the valid area of that tile you must intersect michael@0: // (aTileOrigin.x, aTileOrigin.y, michael@0: // GetScaledTileSize().width, GetScaledTileSize().height) michael@0: // and GetValidRegion() to get the area of the tile that is valid. michael@0: Tile GetTile(const nsIntPoint& aTileOrigin) const; michael@0: michael@0: // Given a tile x, y relative to the top left of the layer, this function michael@0: // will return the tile for michael@0: // (x*GetScaledTileSize().width, y*GetScaledTileSize().height, michael@0: // GetScaledTileSize().width, GetScaledTileSize().height) michael@0: Tile GetTile(int x, int y) const; michael@0: michael@0: // This operates the same as GetTile(aTileOrigin), but will also replace the michael@0: // specified tile with the placeholder tile. This does not call ReleaseTile michael@0: // on the removed tile. michael@0: bool RemoveTile(const nsIntPoint& aTileOrigin, Tile& aRemovedTile); michael@0: michael@0: // This operates the same as GetTile(x, y), but will also replace the michael@0: // specified tile with the placeholder tile. This does not call ReleaseTile michael@0: // on the removed tile. michael@0: bool RemoveTile(int x, int y, Tile& aRemovedTile); michael@0: michael@0: const gfx::IntSize& GetTileSize() const { return mTileSize; } michael@0: michael@0: gfx::IntSize GetScaledTileSize() const { return RoundedToInt(gfx::Size(mTileSize) / mResolution); } michael@0: michael@0: unsigned int GetTileCount() const { return mRetainedTiles.Length(); } michael@0: michael@0: const nsIntRegion& GetValidRegion() const { return mValidRegion; } michael@0: const nsIntRegion& GetPaintedRegion() const { return mPaintedRegion; } michael@0: void ClearPaintedRegion() { mPaintedRegion.SetEmpty(); } michael@0: michael@0: // Given a position i, this function returns the position inside the current tile. michael@0: int GetTileStart(int i, int aTileLength) const { michael@0: return (i >= 0) ? (i % aTileLength) michael@0: : ((aTileLength - (-i % aTileLength)) % michael@0: aTileLength); michael@0: } michael@0: michael@0: // Rounds the given coordinate down to the nearest tile boundary. michael@0: int RoundDownToTileEdge(int aX, int aTileLength) const { return aX - GetTileStart(aX, aTileLength); } michael@0: michael@0: // Get and set draw scaling. mResolution affects the resolution at which the michael@0: // contents of the buffer are drawn. mResolution has no effect on the michael@0: // coordinate space of the valid region, but does affect the size of an michael@0: // individual tile's rect in relation to the valid region. michael@0: // Setting the resolution will invalidate the buffer. michael@0: float GetResolution() const { return mResolution; } michael@0: void SetResolution(float aResolution) { michael@0: if (mResolution == aResolution) { michael@0: return; michael@0: } michael@0: michael@0: Update(nsIntRegion(), nsIntRegion()); michael@0: mResolution = aResolution; michael@0: } michael@0: bool IsLowPrecision() const { return mResolution < 1; } michael@0: michael@0: typedef Tile* Iterator; michael@0: Iterator TilesBegin() { return mRetainedTiles.Elements(); } michael@0: Iterator TilesEnd() { return mRetainedTiles.Elements() + mRetainedTiles.Length(); } michael@0: michael@0: protected: michael@0: // The implementor should call Update() to change michael@0: // the new valid region. This implementation will call michael@0: // validateTile on each tile that is dirty, which is left michael@0: // to the implementor. michael@0: void Update(const nsIntRegion& aNewValidRegion, const nsIntRegion& aPaintRegion); michael@0: michael@0: nsIntRegion mValidRegion; michael@0: nsIntRegion mPaintedRegion; michael@0: michael@0: /** michael@0: * mRetainedTiles is a rectangular buffer of mRetainedWidth x mRetainedHeight michael@0: * stored as column major with the same origin as mValidRegion.GetBounds(). michael@0: * Any tile that does not intersect mValidRegion is a PlaceholderTile. michael@0: * Only the region intersecting with mValidRegion should be read from a tile, michael@0: * another other region is assumed to be uninitialized. The contents of the michael@0: * tiles is scaled by mResolution. michael@0: */ michael@0: nsTArray mRetainedTiles; michael@0: int mRetainedWidth; // in tiles michael@0: int mRetainedHeight; // in tiles michael@0: float mResolution; michael@0: gfx::IntSize mTileSize; michael@0: michael@0: private: michael@0: const Derived& AsDerived() const { return *static_cast(this); } michael@0: Derived& AsDerived() { return *static_cast(this); } michael@0: michael@0: bool IsPlaceholder(Tile aTile) const { return aTile == AsDerived().GetPlaceholderTile(); } michael@0: }; michael@0: michael@0: class ClientTiledLayerBuffer; michael@0: class SurfaceDescriptorTiles; michael@0: class ISurfaceAllocator; michael@0: michael@0: // Shadow layers may implement this interface in order to be notified when a michael@0: // tiled layer buffer is updated. michael@0: class TiledLayerComposer michael@0: { michael@0: public: michael@0: /** michael@0: * Update the current retained layer with the updated layer data. michael@0: * It is expected that the tiles described by aTiledDescriptor are all in the michael@0: * ReadLock state, so that the locks can be adopted when recreating a michael@0: * ClientTiledLayerBuffer locally. This lock will be retained until the buffer michael@0: * has completed uploading. michael@0: */ michael@0: virtual void UseTiledLayerBuffer(ISurfaceAllocator* aAllocator, michael@0: const SurfaceDescriptorTiles& aTiledDescriptor) = 0; michael@0: michael@0: /** michael@0: * If some part of the buffer is being rendered at a lower precision, this michael@0: * returns that region. If it is not, an empty region will be returned. michael@0: */ michael@0: virtual const nsIntRegion& GetValidLowPrecisionRegion() const = 0; michael@0: michael@0: #if defined(MOZ_WIDGET_GONK) && ANDROID_VERSION >= 17 michael@0: /** michael@0: * Store a fence that will signal when the current buffer is no longer being read. michael@0: * Similar to android's GLConsumer::setReleaseFence() michael@0: */ michael@0: virtual void SetReleaseFence(const android::sp& aReleaseFence) = 0; michael@0: #endif michael@0: }; michael@0: michael@0: // Normal integer division truncates towards zero, michael@0: // we instead want to floor to hangle negative numbers. michael@0: static inline int floor_div(int a, int b) michael@0: { michael@0: int rem = a % b; michael@0: int div = a/b; michael@0: if (rem == 0) { michael@0: return div; michael@0: } else { michael@0: // If the signs are different substract 1. michael@0: int sub; michael@0: sub = a ^ b; michael@0: // The results of this shift is either 0 or -1. michael@0: sub >>= 8*sizeof(int)-1; michael@0: return div+sub; michael@0: } michael@0: } michael@0: michael@0: template Tile michael@0: TiledLayerBuffer::GetTile(const nsIntPoint& aTileOrigin) const michael@0: { michael@0: // TODO Cache firstTileOriginX/firstTileOriginY michael@0: // Find the tile x/y of the first tile and the target tile relative to the (0, 0) michael@0: // origin, the difference is the tile x/y relative to the start of the tile buffer. michael@0: gfx::IntSize scaledTileSize = GetScaledTileSize(); michael@0: int firstTileX = floor_div(mValidRegion.GetBounds().x, scaledTileSize.width); michael@0: int firstTileY = floor_div(mValidRegion.GetBounds().y, scaledTileSize.height); michael@0: return GetTile(floor_div(aTileOrigin.x, scaledTileSize.width) - firstTileX, michael@0: floor_div(aTileOrigin.y, scaledTileSize.height) - firstTileY); michael@0: } michael@0: michael@0: template Tile michael@0: TiledLayerBuffer::GetTile(int x, int y) const michael@0: { michael@0: int index = x * mRetainedHeight + y; michael@0: return mRetainedTiles.SafeElementAt(index, AsDerived().GetPlaceholderTile()); michael@0: } michael@0: michael@0: template bool michael@0: TiledLayerBuffer::RemoveTile(const nsIntPoint& aTileOrigin, michael@0: Tile& aRemovedTile) michael@0: { michael@0: gfx::IntSize scaledTileSize = GetScaledTileSize(); michael@0: int firstTileX = floor_div(mValidRegion.GetBounds().x, scaledTileSize.width); michael@0: int firstTileY = floor_div(mValidRegion.GetBounds().y, scaledTileSize.height); michael@0: return RemoveTile(floor_div(aTileOrigin.x, scaledTileSize.width) - firstTileX, michael@0: floor_div(aTileOrigin.y, scaledTileSize.height) - firstTileY, michael@0: aRemovedTile); michael@0: } michael@0: michael@0: template bool michael@0: TiledLayerBuffer::RemoveTile(int x, int y, Tile& aRemovedTile) michael@0: { michael@0: int index = x * mRetainedHeight + y; michael@0: const Tile& tileToRemove = mRetainedTiles.SafeElementAt(index, AsDerived().GetPlaceholderTile()); michael@0: if (!IsPlaceholder(tileToRemove)) { michael@0: aRemovedTile = tileToRemove; michael@0: mRetainedTiles[index] = AsDerived().GetPlaceholderTile(); michael@0: return true; michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: template void michael@0: TiledLayerBuffer::Update(const nsIntRegion& aNewValidRegion, michael@0: const nsIntRegion& aPaintRegion) michael@0: { michael@0: gfx::IntSize scaledTileSize = GetScaledTileSize(); michael@0: michael@0: nsTArray newRetainedTiles; michael@0: nsTArray& oldRetainedTiles = mRetainedTiles; michael@0: const nsIntRect oldBound = mValidRegion.GetBounds(); michael@0: const nsIntRect newBound = aNewValidRegion.GetBounds(); michael@0: const nsIntPoint oldBufferOrigin(RoundDownToTileEdge(oldBound.x, scaledTileSize.width), michael@0: RoundDownToTileEdge(oldBound.y, scaledTileSize.height)); michael@0: const nsIntPoint newBufferOrigin(RoundDownToTileEdge(newBound.x, scaledTileSize.width), michael@0: RoundDownToTileEdge(newBound.y, scaledTileSize.height)); michael@0: const nsIntRegion& oldValidRegion = mValidRegion; michael@0: const nsIntRegion& newValidRegion = aNewValidRegion; michael@0: const int oldRetainedHeight = mRetainedHeight; michael@0: michael@0: // Pass 1: Recycle valid content from the old buffer michael@0: // Recycle tiles from the old buffer that contain valid regions. michael@0: // Insert placeholders tiles if we have no valid area for that tile michael@0: // which we will allocate in pass 2. michael@0: // TODO: Add a tile pool to reduce new allocation michael@0: int tileX = 0; michael@0: int tileY = 0; michael@0: int tilesMissing = 0; michael@0: // Iterate over the new drawing bounds in steps of tiles. michael@0: for (int32_t x = newBound.x; x < newBound.XMost(); tileX++) { michael@0: // Compute tileRect(x,y,width,height) in layer space coordinate michael@0: // giving us the rect of the tile that hits the newBounds. michael@0: int width = scaledTileSize.width - GetTileStart(x, scaledTileSize.width); michael@0: if (x + width > newBound.XMost()) { michael@0: width = newBound.x + newBound.width - x; michael@0: } michael@0: michael@0: tileY = 0; michael@0: for (int32_t y = newBound.y; y < newBound.YMost(); tileY++) { michael@0: int height = scaledTileSize.height - GetTileStart(y, scaledTileSize.height); michael@0: if (y + height > newBound.y + newBound.height) { michael@0: height = newBound.y + newBound.height - y; michael@0: } michael@0: michael@0: const nsIntRect tileRect(x,y,width,height); michael@0: if (oldValidRegion.Intersects(tileRect) && newValidRegion.Intersects(tileRect)) { michael@0: // This old tiles contains some valid area so move it to the new tile michael@0: // buffer. Replace the tile in the old buffer with a placeholder michael@0: // to leave the old buffer index unaffected. michael@0: int tileX = floor_div(x - oldBufferOrigin.x, scaledTileSize.width); michael@0: int tileY = floor_div(y - oldBufferOrigin.y, scaledTileSize.height); michael@0: int index = tileX * oldRetainedHeight + tileY; michael@0: michael@0: // The tile may have been removed, skip over it in this case. michael@0: if (IsPlaceholder(oldRetainedTiles. michael@0: SafeElementAt(index, AsDerived().GetPlaceholderTile()))) { michael@0: newRetainedTiles.AppendElement(AsDerived().GetPlaceholderTile()); michael@0: } else { michael@0: Tile tileWithPartialValidContent = oldRetainedTiles[index]; michael@0: newRetainedTiles.AppendElement(tileWithPartialValidContent); michael@0: oldRetainedTiles[index] = AsDerived().GetPlaceholderTile(); michael@0: } michael@0: michael@0: } else { michael@0: // This tile is either: michael@0: // 1) Outside the new valid region and will simply be an empty michael@0: // placeholder forever. michael@0: // 2) The old buffer didn't have any data for this tile. We postpone michael@0: // the allocation of this tile after we've reused any tile with michael@0: // valid content because then we know we can safely recycle michael@0: // with taking from a tile that has recyclable content. michael@0: newRetainedTiles.AppendElement(AsDerived().GetPlaceholderTile()); michael@0: michael@0: if (aPaintRegion.Intersects(tileRect)) { michael@0: tilesMissing++; michael@0: } michael@0: } michael@0: michael@0: y += height; michael@0: } michael@0: michael@0: x += width; michael@0: } michael@0: michael@0: // Keep track of the number of horizontal/vertical tiles michael@0: // in the buffer so that we can easily look up a tile. michael@0: mRetainedWidth = tileX; michael@0: mRetainedHeight = tileY; michael@0: michael@0: // Pass 1.5: Release excess tiles in oldRetainedTiles michael@0: // Tiles in oldRetainedTiles that aren't in newRetainedTiles will be recycled michael@0: // before creating new ones, but there could still be excess unnecessary michael@0: // tiles. As tiles may not have a fixed memory cost (for example, due to michael@0: // double-buffering), we should release these excess tiles first. michael@0: int oldTileCount = 0; michael@0: for (size_t i = 0; i < oldRetainedTiles.Length(); i++) { michael@0: Tile oldTile = oldRetainedTiles[i]; michael@0: if (IsPlaceholder(oldTile)) { michael@0: continue; michael@0: } michael@0: michael@0: if (oldTileCount >= tilesMissing) { michael@0: oldRetainedTiles[i] = AsDerived().GetPlaceholderTile(); michael@0: AsDerived().ReleaseTile(oldTile); michael@0: } else { michael@0: oldTileCount ++; michael@0: } michael@0: } michael@0: michael@0: NS_ABORT_IF_FALSE(aNewValidRegion.Contains(aPaintRegion), "Painting a region outside the visible region"); michael@0: #ifdef DEBUG michael@0: nsIntRegion oldAndPainted(oldValidRegion); michael@0: oldAndPainted.Or(oldAndPainted, aPaintRegion); michael@0: #endif michael@0: NS_ABORT_IF_FALSE(oldAndPainted.Contains(newValidRegion), "newValidRegion has not been fully painted"); michael@0: michael@0: nsIntRegion regionToPaint(aPaintRegion); michael@0: michael@0: // Pass 2: Validate michael@0: // We know at this point that any tile in the new buffer that had valid content michael@0: // from the previous buffer is placed correctly in the new buffer. michael@0: // We know that any tile in the old buffer that isn't a place holder is michael@0: // of no use and can be recycled. michael@0: // We also know that any place holder tile in the new buffer must be michael@0: // allocated. michael@0: tileX = 0; michael@0: #ifdef GFX_TILEDLAYER_PREF_WARNINGS michael@0: printf_stderr("Update %i, %i, %i, %i\n", newBound.x, newBound.y, newBound.width, newBound.height); michael@0: #endif michael@0: for (int x = newBound.x; x < newBound.x + newBound.width; tileX++) { michael@0: // Compute tileRect(x,y,width,height) in layer space coordinate michael@0: // giving us the rect of the tile that hits the newBounds. michael@0: int tileStartX = RoundDownToTileEdge(x, scaledTileSize.width); michael@0: int width = scaledTileSize.width - GetTileStart(x, scaledTileSize.width); michael@0: if (x + width > newBound.XMost()) michael@0: width = newBound.XMost() - x; michael@0: michael@0: tileY = 0; michael@0: for (int y = newBound.y; y < newBound.y + newBound.height; tileY++) { michael@0: int tileStartY = RoundDownToTileEdge(y, scaledTileSize.height); michael@0: int height = scaledTileSize.height - GetTileStart(y, scaledTileSize.height); michael@0: if (y + height > newBound.YMost()) { michael@0: height = newBound.YMost() - y; michael@0: } michael@0: michael@0: const nsIntRect tileRect(x, y, width, height); michael@0: michael@0: nsIntRegion tileDrawRegion; michael@0: tileDrawRegion.And(tileRect, regionToPaint); michael@0: michael@0: if (tileDrawRegion.IsEmpty()) { michael@0: // We have a tile but it doesn't hit the draw region michael@0: // because we can reuse all of the content from the michael@0: // previous buffer. michael@0: #ifdef DEBUG michael@0: int currTileX = floor_div(x - newBufferOrigin.x, scaledTileSize.width); michael@0: int currTileY = floor_div(y - newBufferOrigin.y, scaledTileSize.height); michael@0: int index = currTileX * mRetainedHeight + currTileY; michael@0: NS_ABORT_IF_FALSE(!newValidRegion.Intersects(tileRect) || michael@0: !IsPlaceholder(newRetainedTiles. michael@0: SafeElementAt(index, AsDerived().GetPlaceholderTile())), michael@0: "If we don't draw a tile we shouldn't have a placeholder there."); michael@0: #endif michael@0: y += height; michael@0: continue; michael@0: } michael@0: michael@0: int tileX = floor_div(x - newBufferOrigin.x, scaledTileSize.width); michael@0: int tileY = floor_div(y - newBufferOrigin.y, scaledTileSize.height); michael@0: int index = tileX * mRetainedHeight + tileY; michael@0: NS_ABORT_IF_FALSE(index >= 0 && michael@0: static_cast(index) < newRetainedTiles.Length(), michael@0: "index out of range"); michael@0: michael@0: Tile newTile = newRetainedTiles[index]; michael@0: michael@0: // Try to reuse a tile from the old retained tiles that had no partially michael@0: // valid content. michael@0: while (IsPlaceholder(newTile) && oldRetainedTiles.Length() > 0) { michael@0: AsDerived().SwapTiles(newTile, oldRetainedTiles[oldRetainedTiles.Length()-1]); michael@0: oldRetainedTiles.RemoveElementAt(oldRetainedTiles.Length()-1); michael@0: if (!IsPlaceholder(newTile)) { michael@0: oldTileCount--; michael@0: } michael@0: } michael@0: michael@0: // We've done our best effort to recycle a tile but it can be null michael@0: // in which case it's up to the derived class's ValidateTile() michael@0: // implementation to allocate a new tile before drawing michael@0: nsIntPoint tileOrigin(tileStartX, tileStartY); michael@0: newTile = AsDerived().ValidateTile(newTile, nsIntPoint(tileStartX, tileStartY), michael@0: tileDrawRegion); michael@0: NS_ABORT_IF_FALSE(!IsPlaceholder(newTile), "index out of range"); michael@0: #ifdef GFX_TILEDLAYER_PREF_WARNINGS michael@0: printf_stderr("Store Validate tile %i, %i -> %i\n", tileStartX, tileStartY, index); michael@0: #endif michael@0: newRetainedTiles[index] = newTile; michael@0: michael@0: y += height; michael@0: } michael@0: michael@0: x += width; michael@0: } michael@0: michael@0: // At this point, oldTileCount should be zero michael@0: NS_ABORT_IF_FALSE(oldTileCount == 0, "Failed to release old tiles"); michael@0: michael@0: mRetainedTiles = newRetainedTiles; michael@0: mValidRegion = aNewValidRegion; michael@0: mPaintedRegion.Or(mPaintedRegion, aPaintRegion); michael@0: } michael@0: michael@0: } // layers michael@0: } // mozilla michael@0: michael@0: #endif // GFX_TILEDLAYERBUFFER_H