blob: e33d39acf9fdb22e493c6dd0914c2dd8a954339b [file] [log] [blame]
/*
* Copyright 2010, The Android Open Source Project
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "config.h"
#include "TiledPage.h"
#if USE(ACCELERATED_COMPOSITING)
#include "GLUtils.h"
#include "IntRect.h"
#include "PaintTileOperation.h"
#include "SkPaint.h"
#include "SkPaintFlagsDrawFilter.h"
#include "TilesManager.h"
#include <cutils/log.h>
#include <wtf/CurrentTime.h>
#include <wtf/text/CString.h>
#undef XLOGC
#define XLOGC(...) android_printLog(ANDROID_LOG_DEBUG, "TiledPage", __VA_ARGS__)
#ifdef DEBUG
#undef XLOG
#define XLOG(...) android_printLog(ANDROID_LOG_DEBUG, "TiledPage", __VA_ARGS__)
#else
#undef XLOG
#define XLOG(...)
#endif // DEBUG
namespace WebCore {
using namespace android;
TiledPage::TiledPage(int id, GLWebViewState* state)
: m_baseTiles(0)
, m_baseTileSize(0)
, m_id(id)
, m_scale(1)
, m_invScale(1)
, m_glWebViewState(state)
, m_latestPictureInval(0)
, m_prepare(false)
, m_isPrefetchPage(false)
, m_willDraw(false)
{
m_baseTiles = new BaseTile[TilesManager::getMaxTextureAllocation() + 1];
#ifdef DEBUG_COUNT
ClassTracker::instance()->increment("TiledPage");
#endif
}
void TiledPage::updateBaseTileSize()
{
// This value must be at least 1 greater than the max number of allowed
// textures. This is because prepare() asks for a tile before it reserves
// a texture for that tile. If all textures are currently in use by the
// page then there will be no available tile and having the extra tile
// ensures that this does not happen. After claiming the extra tile the call
// to reserveTexture() will cause some other tile in the page to lose it's
// texture and become available, thus ensuring that we always have at least
// one tile that is available.
int baseTileSize = TilesManager::instance()->maxTextureCount() + 1;
if (baseTileSize > m_baseTileSize)
m_baseTileSize = baseTileSize;
}
TiledPage::~TiledPage()
{
// In order to delete the page we must ensure that none of its BaseTiles are
// currently painting or scheduled to be painted by the TextureGenerator
TilesManager::instance()->removeOperationsForPage(this);
delete[] m_baseTiles;
#ifdef DEBUG_COUNT
ClassTracker::instance()->decrement("TiledPage");
#endif
}
BaseTile* TiledPage::getBaseTile(int x, int y) const
{
// TODO: replace loop over array with HashMap indexing
for (int j = 0; j < m_baseTileSize; j++) {
BaseTile& tile = m_baseTiles[j];
if (tile.x() == x && tile.y() == y)
return &tile;
}
return 0;
}
void TiledPage::discardTextures()
{
for (int j = 0; j < m_baseTileSize; j++) {
BaseTile& tile = m_baseTiles[j];
tile.discardTextures();
}
return;
}
void TiledPage::invalidateRect(const IntRect& inval, const unsigned int pictureCount)
{
// Given the current scale level we need to mark the appropriate tiles as dirty
const float invTileContentWidth = m_scale / TilesManager::tileWidth();
const float invTileContentHeight = m_scale / TilesManager::tileHeight();
const int firstDirtyTileX = static_cast<int>(floorf(inval.x() * invTileContentWidth));
const int firstDirtyTileY = static_cast<int>(floorf(inval.y() * invTileContentHeight));
const int lastDirtyTileX = static_cast<int>(ceilf(inval.maxX() * invTileContentWidth));
const int lastDirtyTileY = static_cast<int>(ceilf(inval.maxY() * invTileContentHeight));
XLOG("Marking X %d-%d and Y %d-%d dirty", firstDirtyTileX, lastDirtyTileX, firstDirtyTileY, lastDirtyTileY);
// We defer marking the tile as dirty until the next time we need to prepare
// to draw.
m_invalRegion.op(firstDirtyTileX, firstDirtyTileY, lastDirtyTileX, lastDirtyTileY, SkRegion::kUnion_Op);
m_invalTilesRegion.op(inval.x(), inval.y(), inval.maxX(), inval.maxY(), SkRegion::kUnion_Op);
m_latestPictureInval = pictureCount;
}
void TiledPage::prepareRow(bool goingLeft, int tilesInRow, int firstTileX, int y, const SkIRect& tileBounds)
{
for (int i = 0; i < tilesInRow; i++) {
int x = firstTileX;
// If we are goingLeft, we want to schedule the tiles starting from the
// right (and to the left if not). This is because tiles are appended to
// the list and the texture uploader goes through the set front to back.
if (goingLeft)
x += (tilesInRow - 1) - i;
else
x += i;
BaseTile* currentTile = 0;
BaseTile* availableTile = 0;
for (int j = 0; j < m_baseTileSize; j++) {
BaseTile& tile = m_baseTiles[j];
if (tile.x() == x && tile.y() == y) {
currentTile = &tile;
break;
}
if (!availableTile || (tile.drawCount() < availableTile->drawCount()))
availableTile = &tile;
}
if (!currentTile && availableTile) {
XLOG("STEALING tile %d, %d (draw count %llu) for tile %d, %d",
availableTile->x(), availableTile->y(), availableTile->drawCount(), x, y);
availableTile->discardTextures(); // don't wait for textures to be stolen
currentTile = availableTile;
}
if (!currentTile) {
XLOG("ERROR: No tile available for tile %d %d", x, y);
}
if (currentTile) {
currentTile->setGLWebViewState(m_glWebViewState);
currentTile->setPage(this);
currentTile->setContents(this, x, y, m_scale);
// TODO: move below (which is largely the same for layers / tiled
// page) into prepare() function
// ensure there is a texture associated with the tile and then check to
// see if the texture is dirty and in need of repainting
if (currentTile->isDirty() || !currentTile->frontTexture())
currentTile->reserveTexture();
if (currentTile->backTexture()
&& currentTile->isDirty()
&& !currentTile->isRepaintPending()) {
PaintTileOperation *operation = new PaintTileOperation(currentTile);
TilesManager::instance()->scheduleOperation(operation);
}
}
}
}
bool TiledPage::updateTileDirtiness(const SkIRect& tileBounds)
{
if (!m_glWebViewState || tileBounds.isEmpty()) {
m_invalRegion.setEmpty();
m_invalTilesRegion.setEmpty();
return false;
}
bool visibleTileIsDirty = false;
for (int x = 0; x < m_baseTileSize; x++) {
BaseTile& tile = m_baseTiles[x];
// if the tile is in the dirty region then we must invalidate it
if (m_invalRegion.contains(tile.x(), tile.y())) {
tile.markAsDirty(m_latestPictureInval, m_invalTilesRegion);
if (tileBounds.contains(tile.x(), tile.y()))
visibleTileIsDirty = true;
}
}
// clear the invalidated region as all tiles within that region have now
// been marked as dirty.
m_invalRegion.setEmpty();
m_invalTilesRegion.setEmpty();
return visibleTileIsDirty;
}
void TiledPage::prepare(bool goingDown, bool goingLeft, const SkIRect& tileBounds, PrepareBounds bounds)
{
if (!m_glWebViewState)
return;
TilesManager::instance()->gatherTextures();
m_scrollingDown = goingDown;
int firstTileX = tileBounds.fLeft;
int firstTileY = tileBounds.fTop;
int nbTilesWidth = tileBounds.width();
int nbTilesHeight = tileBounds.height();
int lastTileX = tileBounds.fRight - 1;
int lastTileY = tileBounds.fBottom - 1;
// Expand number of tiles to allow tiles outside of viewport to be prepared for
// smoother scrolling.
int nTilesToPrepare = nbTilesWidth * nbTilesHeight;
int nMaxTilesPerPage = m_baseTileSize / 2;
if (bounds == ExpandedBounds) {
// prepare tiles outside of the visible bounds
int expandX = m_glWebViewState->expandedTileBoundsX();
int expandY = m_glWebViewState->expandedTileBoundsY();
firstTileX -= expandX;
lastTileX += expandX;
nbTilesWidth += expandX * 2;
firstTileY -= expandY;
lastTileY += expandY;
nbTilesHeight += expandY * 2;
}
// crop the prepared region to the contents of the base layer
float maxWidthTiles = m_glWebViewState->baseContentWidth() * m_scale / TilesManager::tileWidth();
float maxHeightTiles = m_glWebViewState->baseContentHeight() * m_scale / TilesManager::tileHeight();
firstTileX = std::max(0, firstTileX);
firstTileY = std::max(0, firstTileY);
lastTileX = std::min(lastTileX, static_cast<int>(ceilf(maxWidthTiles)) - 1);
lastTileY = std::min(lastTileY, static_cast<int>(ceilf(maxHeightTiles)) - 1);
// check against corrupted scale values giving bad height/width (use float to avoid overflow)
float numTiles = static_cast<float>(nbTilesHeight) * static_cast<float>(nbTilesWidth);
if (numTiles > TilesManager::getMaxTextureAllocation() || nbTilesHeight < 1 || nbTilesWidth < 1)
{
XLOGC("ERROR: We don't have enough tiles for this page!"
" nbTilesHeight %d nbTilesWidth %d", nbTilesHeight, nbTilesWidth);
return;
}
for (int i = 0; i < nbTilesHeight; i++)
prepareRow(goingLeft, nbTilesWidth, firstTileX, firstTileY + i, tileBounds);
m_prepare = true;
}
bool TiledPage::hasMissingContent(const SkIRect& tileBounds)
{
int neededTiles = tileBounds.width() * tileBounds.height();
for (int j = 0; j < m_baseTileSize; j++) {
BaseTile& tile = m_baseTiles[j];
if (tileBounds.contains(tile.x(), tile.y())) {
if (tile.frontTexture())
neededTiles--;
}
}
return neededTiles > 0;
}
bool TiledPage::isReady(const SkIRect& tileBounds)
{
int neededTiles = tileBounds.width() * tileBounds.height();
XLOG("tiled page %p needs %d ready tiles", this, neededTiles);
for (int j = 0; j < m_baseTileSize; j++) {
BaseTile& tile = m_baseTiles[j];
if (tileBounds.contains(tile.x(), tile.y())) {
if (tile.isTileReady())
neededTiles--;
}
}
XLOG("tiled page %p still needs %d ready tiles", this, neededTiles);
return neededTiles == 0;
}
bool TiledPage::swapBuffersIfReady(const SkIRect& tileBounds, float scale)
{
if (!m_glWebViewState)
return false;
if (!m_invalRegion.isEmpty() && !m_prepare)
return false;
if (m_scale != scale)
return false;
int swaps = 0;
bool fullSwap = true;
for (int x = tileBounds.fLeft; x < tileBounds.fRight; x++) {
for (int y = tileBounds.fTop; y < tileBounds.fBottom; y++) {
BaseTile* t = getBaseTile(x, y);
if (!t || !t->isTileReady())
fullSwap = false;
}
}
// swap every tile on page (even if off screen)
for (int j = 0; j < m_baseTileSize; j++) {
BaseTile& tile = m_baseTiles[j];
if (tile.swapTexturesIfNeeded())
swaps++;
}
XLOG("%p greedy swapped %d textures, returning true", this, swaps);
return fullSwap;
}
void TiledPage::prepareForDrawGL(float transparency, const SkIRect& tileBounds)
{
m_willDraw = true;
m_transparency = transparency;
m_tileBounds = tileBounds;
}
void TiledPage::drawGL()
{
if (!m_glWebViewState || m_transparency == 0 || !m_willDraw)
return;
const float tileWidth = TilesManager::tileWidth() * m_invScale;
const float tileHeight = TilesManager::tileHeight() * m_invScale;
for (int j = 0; j < m_baseTileSize; j++) {
BaseTile& tile = m_baseTiles[j];
bool tileInView = m_tileBounds.contains(tile.x(), tile.y());
if (tileInView) {
SkRect rect;
rect.fLeft = tile.x() * tileWidth;
rect.fTop = tile.y() * tileHeight;
rect.fRight = rect.fLeft + tileWidth;
rect.fBottom = rect.fTop + tileHeight;
tile.draw(m_transparency, rect, m_scale);
}
TilesManager::instance()->getProfiler()->nextTile(tile, m_invScale, tileInView);
}
m_willDraw = false; // don't redraw until re-prepared
}
bool TiledPage::paint(BaseTile* tile, SkCanvas* canvas, unsigned int* pictureUsed)
{
static SkPaintFlagsDrawFilter prefetchFilter(SkPaint::kAllFlags,
SkPaint::kAntiAlias_Flag);
if (!m_glWebViewState)
return false;
if (isPrefetchPage())
canvas->setDrawFilter(&prefetchFilter);
*pictureUsed = m_glWebViewState->paintBaseLayerContent(canvas);
return true;
}
TiledPage* TiledPage::sibling()
{
if (!m_glWebViewState)
return 0;
return m_glWebViewState->sibling(this);
}
} // namespace WebCore
#endif // USE(ACCELERATED_COMPOSITING)