Home | History | Annotate | Download | only in android
      1 /*
      2  * Copyright 2010, The Android Open Source Project
      3  *
      4  * Redistribution and use in source and binary forms, with or without
      5  * modification, are permitted provided that the following conditions
      6  * are met:
      7  *  * Redistributions of source code must retain the above copyright
      8  *    notice, this list of conditions and the following disclaimer.
      9  *  * Redistributions in binary form must reproduce the above copyright
     10  *    notice, this list of conditions and the following disclaimer in the
     11  *    documentation and/or other materials provided with the distribution.
     12  *
     13  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY
     14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR
     17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
     18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
     19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
     20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
     21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     24  */
     25 
     26 #include "config.h"
     27 #include "TiledPage.h"
     28 
     29 #if USE(ACCELERATED_COMPOSITING)
     30 
     31 #include "GLUtils.h"
     32 #include "IntRect.h"
     33 #include "PaintTileOperation.h"
     34 #include "SkPaint.h"
     35 #include "SkPaintFlagsDrawFilter.h"
     36 #include "TilesManager.h"
     37 
     38 #include <cutils/log.h>
     39 #include <wtf/CurrentTime.h>
     40 #include <wtf/text/CString.h>
     41 
     42 #undef XLOGC
     43 #define XLOGC(...) android_printLog(ANDROID_LOG_DEBUG, "TiledPage", __VA_ARGS__)
     44 
     45 #ifdef DEBUG
     46 
     47 #undef XLOG
     48 #define XLOG(...) android_printLog(ANDROID_LOG_DEBUG, "TiledPage", __VA_ARGS__)
     49 
     50 #else
     51 
     52 #undef XLOG
     53 #define XLOG(...)
     54 
     55 #endif // DEBUG
     56 
     57 namespace WebCore {
     58 
     59 using namespace android;
     60 
     61 TiledPage::TiledPage(int id, GLWebViewState* state)
     62     : m_baseTiles(0)
     63     , m_baseTileSize(0)
     64     , m_id(id)
     65     , m_scale(1)
     66     , m_invScale(1)
     67     , m_glWebViewState(state)
     68     , m_latestPictureInval(0)
     69     , m_prepare(false)
     70     , m_isPrefetchPage(false)
     71     , m_willDraw(false)
     72 {
     73     m_baseTiles = new BaseTile[TilesManager::getMaxTextureAllocation() + 1];
     74 #ifdef DEBUG_COUNT
     75     ClassTracker::instance()->increment("TiledPage");
     76 #endif
     77 }
     78 
     79 void TiledPage::updateBaseTileSize()
     80 {
     81     // This value must be at least 1 greater than the max number of allowed
     82     // textures. This is because prepare() asks for a tile before it reserves
     83     // a texture for that tile. If all textures are currently in use by the
     84     // page then there will be no available tile and having the extra tile
     85     // ensures that this does not happen. After claiming the extra tile the call
     86     // to reserveTexture() will cause some other tile in the page to lose it's
     87     // texture and become available, thus ensuring that we always have at least
     88     // one tile that is available.
     89     int baseTileSize = TilesManager::instance()->maxTextureCount() + 1;
     90     if (baseTileSize > m_baseTileSize)
     91         m_baseTileSize = baseTileSize;
     92 }
     93 
     94 TiledPage::~TiledPage()
     95 {
     96     // In order to delete the page we must ensure that none of its BaseTiles are
     97     // currently painting or scheduled to be painted by the TextureGenerator
     98     TilesManager::instance()->removeOperationsForPage(this);
     99     delete[] m_baseTiles;
    100 #ifdef DEBUG_COUNT
    101     ClassTracker::instance()->decrement("TiledPage");
    102 #endif
    103 }
    104 
    105 BaseTile* TiledPage::getBaseTile(int x, int y) const
    106 {
    107     // TODO: replace loop over array with HashMap indexing
    108     for (int j = 0; j < m_baseTileSize; j++) {
    109         BaseTile& tile = m_baseTiles[j];
    110         if (tile.x() == x && tile.y() == y)
    111             return &tile;
    112     }
    113     return 0;
    114 }
    115 
    116 void TiledPage::discardTextures()
    117 {
    118     for (int j = 0; j < m_baseTileSize; j++) {
    119         BaseTile& tile = m_baseTiles[j];
    120         tile.discardTextures();
    121     }
    122     return;
    123 }
    124 
    125 void TiledPage::invalidateRect(const IntRect& inval, const unsigned int pictureCount)
    126 {
    127     // Given the current scale level we need to mark the appropriate tiles as dirty
    128     const float invTileContentWidth = m_scale / TilesManager::tileWidth();
    129     const float invTileContentHeight = m_scale / TilesManager::tileHeight();
    130 
    131     const int firstDirtyTileX = static_cast<int>(floorf(inval.x() * invTileContentWidth));
    132     const int firstDirtyTileY = static_cast<int>(floorf(inval.y() * invTileContentHeight));
    133     const int lastDirtyTileX = static_cast<int>(ceilf(inval.maxX() * invTileContentWidth));
    134     const int lastDirtyTileY = static_cast<int>(ceilf(inval.maxY() * invTileContentHeight));
    135 
    136     XLOG("Marking X %d-%d and Y %d-%d dirty", firstDirtyTileX, lastDirtyTileX, firstDirtyTileY, lastDirtyTileY);
    137     // We defer marking the tile as dirty until the next time we need to prepare
    138     // to draw.
    139     m_invalRegion.op(firstDirtyTileX, firstDirtyTileY, lastDirtyTileX, lastDirtyTileY, SkRegion::kUnion_Op);
    140     m_invalTilesRegion.op(inval.x(), inval.y(), inval.maxX(), inval.maxY(), SkRegion::kUnion_Op);
    141     m_latestPictureInval = pictureCount;
    142 }
    143 
    144 void TiledPage::prepareRow(bool goingLeft, int tilesInRow, int firstTileX, int y, const SkIRect& tileBounds)
    145 {
    146     for (int i = 0; i < tilesInRow; i++) {
    147         int x = firstTileX;
    148 
    149         // If we are goingLeft, we want to schedule the tiles starting from the
    150         // right (and to the left if not). This is because tiles are appended to
    151         // the list and the texture uploader goes through the set front to back.
    152         if (goingLeft)
    153             x += (tilesInRow - 1) - i;
    154         else
    155             x += i;
    156 
    157         BaseTile* currentTile = 0;
    158         BaseTile* availableTile = 0;
    159         for (int j = 0; j < m_baseTileSize; j++) {
    160             BaseTile& tile = m_baseTiles[j];
    161             if (tile.x() == x && tile.y() == y) {
    162                 currentTile = &tile;
    163                 break;
    164             }
    165 
    166             if (!availableTile || (tile.drawCount() < availableTile->drawCount()))
    167                 availableTile = &tile;
    168         }
    169 
    170         if (!currentTile && availableTile) {
    171             XLOG("STEALING tile %d, %d (draw count %llu) for tile %d, %d",
    172                   availableTile->x(), availableTile->y(), availableTile->drawCount(), x, y);
    173             availableTile->discardTextures(); // don't wait for textures to be stolen
    174             currentTile = availableTile;
    175         }
    176 
    177         if (!currentTile) {
    178             XLOG("ERROR: No tile available for tile %d %d", x, y);
    179         }
    180 
    181         if (currentTile) {
    182             currentTile->setGLWebViewState(m_glWebViewState);
    183             currentTile->setPage(this);
    184 
    185             currentTile->setContents(this, x, y, m_scale);
    186 
    187             // TODO: move below (which is largely the same for layers / tiled
    188             // page) into prepare() function
    189 
    190             // ensure there is a texture associated with the tile and then check to
    191             // see if the texture is dirty and in need of repainting
    192             if (currentTile->isDirty() || !currentTile->frontTexture())
    193                 currentTile->reserveTexture();
    194             if (currentTile->backTexture()
    195                     && currentTile->isDirty()
    196                     && !currentTile->isRepaintPending()) {
    197                 PaintTileOperation *operation = new PaintTileOperation(currentTile);
    198                 TilesManager::instance()->scheduleOperation(operation);
    199             }
    200         }
    201     }
    202 }
    203 
    204 bool TiledPage::updateTileDirtiness(const SkIRect& tileBounds)
    205 {
    206     if (!m_glWebViewState || tileBounds.isEmpty()) {
    207         m_invalRegion.setEmpty();
    208         m_invalTilesRegion.setEmpty();
    209         return false;
    210     }
    211 
    212     bool visibleTileIsDirty = false;
    213     for (int x = 0; x < m_baseTileSize; x++) {
    214 
    215         BaseTile& tile = m_baseTiles[x];
    216 
    217         // if the tile is in the dirty region then we must invalidate it
    218         if (m_invalRegion.contains(tile.x(), tile.y())) {
    219             tile.markAsDirty(m_latestPictureInval, m_invalTilesRegion);
    220             if (tileBounds.contains(tile.x(), tile.y()))
    221                 visibleTileIsDirty = true;
    222         }
    223     }
    224 
    225     // clear the invalidated region as all tiles within that region have now
    226     // been marked as dirty.
    227     m_invalRegion.setEmpty();
    228     m_invalTilesRegion.setEmpty();
    229     return visibleTileIsDirty;
    230 }
    231 
    232 void TiledPage::prepare(bool goingDown, bool goingLeft, const SkIRect& tileBounds, PrepareBounds bounds)
    233 {
    234     if (!m_glWebViewState)
    235         return;
    236 
    237     TilesManager::instance()->gatherTextures();
    238     m_scrollingDown = goingDown;
    239 
    240     int firstTileX = tileBounds.fLeft;
    241     int firstTileY = tileBounds.fTop;
    242     int nbTilesWidth = tileBounds.width();
    243     int nbTilesHeight = tileBounds.height();
    244 
    245     // Expand number of tiles to allow tiles outside of viewport to be prepared for
    246     // smoother scrolling.
    247     int nTilesToPrepare = nbTilesWidth * nbTilesHeight;
    248     int nMaxTilesPerPage = m_baseTileSize / 2;
    249 
    250     if (bounds == ExpandedBounds) {
    251         // prepare tiles outside of the visible bounds
    252         int expandX = m_glWebViewState->expandedTileBoundsX();
    253         int expandY = m_glWebViewState->expandedTileBoundsY();
    254 
    255         firstTileX -= expandX;
    256         nbTilesWidth += expandX * 2;
    257 
    258         firstTileY -= expandY;
    259         nbTilesHeight += expandY * 2;
    260     }
    261 
    262     // crop the tile bounds in each dimension to the larger of the base layer or viewport
    263     float maxBaseX = m_glWebViewState->baseContentWidth() * m_scale / TilesManager::tileWidth();
    264     float maxBaseY = m_glWebViewState->baseContentHeight() * m_scale / TilesManager::tileHeight();
    265     int maxX = std::max(static_cast<int>(ceilf(maxBaseX)),
    266                         m_glWebViewState->viewportTileBounds().width());
    267     int maxY = std::max(static_cast<int>(ceilf(maxBaseY)),
    268                         m_glWebViewState->viewportTileBounds().height());
    269 
    270     // adjust perimeter to not go outside cropped region
    271     if (firstTileX < 0) {
    272         nbTilesWidth += firstTileX;
    273         firstTileX = 0;
    274     }
    275     if (firstTileY < 0) {
    276         nbTilesHeight += firstTileY;
    277         firstTileY = 0;
    278     }
    279     nbTilesWidth = std::min(nbTilesWidth, maxX - firstTileX);
    280     nbTilesHeight = std::min(nbTilesHeight, maxY - firstTileY);
    281 
    282     // check against corrupted scale values giving bad height/width (use float to avoid overflow)
    283     float numTiles = static_cast<float>(nbTilesHeight) * static_cast<float>(nbTilesWidth);
    284     if (numTiles > TilesManager::getMaxTextureAllocation() || nbTilesHeight < 1 || nbTilesWidth < 1)
    285     {
    286         XLOGC("ERROR: We don't have enough tiles for this page!"
    287               " nbTilesHeight %d nbTilesWidth %d", nbTilesHeight, nbTilesWidth);
    288         return;
    289     }
    290     for (int i = 0; i < nbTilesHeight; i++)
    291         prepareRow(goingLeft, nbTilesWidth, firstTileX, firstTileY + i, tileBounds);
    292 
    293     m_prepare = true;
    294 }
    295 
    296 bool TiledPage::hasMissingContent(const SkIRect& tileBounds)
    297 {
    298     int neededTiles = tileBounds.width() * tileBounds.height();
    299     for (int j = 0; j < m_baseTileSize; j++) {
    300         BaseTile& tile = m_baseTiles[j];
    301         if (tileBounds.contains(tile.x(), tile.y())) {
    302             if (tile.frontTexture())
    303                 neededTiles--;
    304         }
    305     }
    306     return neededTiles > 0;
    307 }
    308 
    309 bool TiledPage::isReady(const SkIRect& tileBounds)
    310 {
    311     int neededTiles = tileBounds.width() * tileBounds.height();
    312     XLOG("tiled page %p needs %d ready tiles", this, neededTiles);
    313     for (int j = 0; j < m_baseTileSize; j++) {
    314         BaseTile& tile = m_baseTiles[j];
    315         if (tileBounds.contains(tile.x(), tile.y())) {
    316             if (tile.isTileReady())
    317                 neededTiles--;
    318         }
    319     }
    320     XLOG("tiled page %p still needs %d ready tiles", this, neededTiles);
    321     return neededTiles == 0;
    322 }
    323 
    324 bool TiledPage::swapBuffersIfReady(const SkIRect& tileBounds, float scale)
    325 {
    326     if (!m_glWebViewState)
    327         return false;
    328 
    329     if (!m_invalRegion.isEmpty() && !m_prepare)
    330         return false;
    331 
    332     if (m_scale != scale)
    333         return false;
    334 
    335     int swaps = 0;
    336     bool fullSwap = true;
    337     for (int x = tileBounds.fLeft; x < tileBounds.fRight; x++) {
    338         for (int y = tileBounds.fTop; y < tileBounds.fBottom; y++) {
    339             BaseTile* t = getBaseTile(x, y);
    340             if (!t || !t->isTileReady())
    341                 fullSwap = false;
    342         }
    343     }
    344 
    345     // swap every tile on page (even if off screen)
    346     for (int j = 0; j < m_baseTileSize; j++) {
    347         BaseTile& tile = m_baseTiles[j];
    348         if (tile.swapTexturesIfNeeded())
    349             swaps++;
    350     }
    351 
    352     XLOG("%p greedy swapped %d textures, returning true", this, swaps);
    353     return fullSwap;
    354 }
    355 
    356 void TiledPage::prepareForDrawGL(float transparency, const SkIRect& tileBounds)
    357 {
    358     m_willDraw = true;
    359     m_transparency = transparency;
    360     m_tileBounds = tileBounds;
    361 }
    362 
    363 void TiledPage::drawGL()
    364 {
    365     if (!m_glWebViewState || m_transparency == 0 || !m_willDraw)
    366         return;
    367 
    368     const float tileWidth = TilesManager::tileWidth() * m_invScale;
    369     const float tileHeight = TilesManager::tileHeight() * m_invScale;
    370 
    371     for (int j = 0; j < m_baseTileSize; j++) {
    372         BaseTile& tile = m_baseTiles[j];
    373         bool tileInView = m_tileBounds.contains(tile.x(), tile.y());
    374         if (tileInView) {
    375             SkRect rect;
    376             rect.fLeft = tile.x() * tileWidth;
    377             rect.fTop = tile.y() * tileHeight;
    378             rect.fRight = rect.fLeft + tileWidth;
    379             rect.fBottom = rect.fTop + tileHeight;
    380 
    381             tile.draw(m_transparency, rect, m_scale);
    382         }
    383 
    384         TilesManager::instance()->getProfiler()->nextTile(tile, m_invScale, tileInView);
    385     }
    386     m_willDraw = false; // don't redraw until re-prepared
    387 }
    388 
    389 bool TiledPage::paint(BaseTile* tile, SkCanvas* canvas, unsigned int* pictureUsed)
    390 {
    391     static SkPaintFlagsDrawFilter prefetchFilter(SkPaint::kAllFlags,
    392                                                  SkPaint::kAntiAlias_Flag);
    393 
    394     if (!m_glWebViewState)
    395         return false;
    396 
    397     if (isPrefetchPage())
    398         canvas->setDrawFilter(&prefetchFilter);
    399 
    400     *pictureUsed = m_glWebViewState->paintBaseLayerContent(canvas);
    401     return true;
    402 }
    403 
    404 TiledPage* TiledPage::sibling()
    405 {
    406     if (!m_glWebViewState)
    407         return 0;
    408     return m_glWebViewState->sibling(this);
    409 }
    410 
    411 } // namespace WebCore
    412 
    413 #endif // USE(ACCELERATED_COMPOSITING)
    414