/* * Copyright 2011, 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 "TiledTexture.h" #include "TilesManager.h" #include "TilesTracker.h" #include "PaintedSurface.h" #include "PaintTileOperation.h" #include "SkCanvas.h" #include "SkPicture.h" #include #include #include #undef XLOGC #define XLOGC(...) android_printLog(ANDROID_LOG_DEBUG, "TiledTexture", __VA_ARGS__) #ifdef DEBUG #undef XLOG #define XLOG(...) android_printLog(ANDROID_LOG_DEBUG, "TiledTexture", __VA_ARGS__) #else #undef XLOG #define XLOG(...) #endif // DEBUG namespace WebCore { TiledTexture::~TiledTexture() { SkSafeUnref(m_paintingPicture); #ifdef DEBUG_COUNT ClassTracker::instance()->decrement("TiledTexture"); #endif removeTiles(); } bool TiledTexture::ready() { bool tilesAllReady = true; bool tilesVisible = false; for (unsigned int i = 0; i < m_tiles.size(); i++) { BaseTile* tile = m_tiles[i]; if (tile->isTileVisible(m_area)) { tilesVisible = true; if (!tile->isTileReady()) { tilesAllReady = false; break; } } } // For now, if no textures are available, consider ourselves as ready // in order to unblock the zooming process. // FIXME: have a better system -- maybe keeping the last scale factor // able to fully render everything XLOG("TT %p, ready %d, visible %d, texturesRemain %d", this, tilesAllReady, tilesVisible, TilesManager::instance()->layerTexturesRemain()); return !TilesManager::instance()->layerTexturesRemain() || !tilesVisible || tilesAllReady; } void TiledTexture::swapTiles() { int swaps = 0; for (unsigned int i = 0; i < m_tiles.size(); i++) if (m_tiles[i]->swapTexturesIfNeeded()) swaps++; XLOG("TT %p swapping, swaps = %d", this, swaps); } IntRect TiledTexture::computeTilesArea(IntRect& contentArea, float scale) { IntRect computedArea; IntRect area(contentArea.x() * scale, contentArea.y() * scale, ceilf(contentArea.width() * scale), ceilf(contentArea.height() * scale)); XLOG("TT %p prepare, scale %f, area %d x %d", this, scale, area.width(), area.height()); if (area.width() == 0 && area.height() == 0) { computedArea.setWidth(0); computedArea.setHeight(0); return computedArea; } int tileWidth = TilesManager::instance()->layerTileWidth(); int tileHeight = TilesManager::instance()->layerTileHeight(); computedArea.setX(area.x() / tileWidth); computedArea.setY(area.y() / tileHeight); float right = (area.x() + area.width()) / (float) tileWidth; float bottom = (area.y() + area.height()) / (float) tileHeight; computedArea.setWidth(ceilf(right) - computedArea.x()); computedArea.setHeight(ceilf(bottom) - computedArea.y()); return computedArea; } void TiledTexture::prepare(GLWebViewState* state, float scale, bool repaint, bool startFastSwap, IntRect& prepareArea) { if (!m_surface) return; // first, how many tiles do we need m_area = computeTilesArea(prepareArea, scale); if (m_area.isEmpty()) return; XLOG("for TiledTexture %p, we prepare with scale %.2f, have a prepare area of " " %d, %d - %d x %d, corresponding to %d, %d x - %d x %d tiles", this, scale, prepareArea.x(), prepareArea.y(), prepareArea.width(), prepareArea.height(), m_area.x(), m_area.y(), m_area.width(), m_area.height()); bool goingDown = m_prevTileY < m_area.y(); m_prevTileY = m_area.y(); if (scale != m_scale) TilesManager::instance()->removeOperationsForFilter(new ScaleFilter(this, scale)); m_scale = scale; // apply dirty region to affected tiles if (!m_dirtyRegion.isEmpty()) { for (unsigned int i = 0; i < m_tiles.size(); i++) { // TODO: don't mark all tiles dirty m_tiles[i]->markAsDirty(1, m_dirtyRegion); } } m_dirtyRegion.setEmpty(); for (int i = 0; i < m_area.width(); i++) { if (goingDown) { for (int j = 0; j < m_area.height(); j++) { prepareTile(repaint, m_area.x() + i, m_area.y() + j); } } else { for (int j = m_area.height() - 1; j >= 0; j--) { prepareTile(repaint, m_area.x() + i, m_area.y() + j); } } } } void TiledTexture::update(const SkRegion& invalRegion, SkPicture* picture) { XLOG("TT %p update, current region empty %d, new empty %d, painting picture %p", this, m_dirtyRegion.isEmpty(), invalRegion.isEmpty(), picture); m_dirtyRegion.op(invalRegion, SkRegion::kUnion_Op); android::Mutex::Autolock lock(m_paintingPictureSync); SkSafeRef(picture); SkSafeUnref(m_paintingPicture); m_paintingPicture = picture; } void TiledTexture::prepareTile(bool repaint, int x, int y) { BaseTile* tile = getTile(x, y); if (!tile) { tile = new BaseTile(true); m_tiles.append(tile); } XLOG("preparing tile %p at %d, %d, painter is this %p", tile, x, y, this); tile->setContents(this, x, y, m_scale); // TODO: move below (which is largely the same for layers / tiled page) into // prepare() function if (tile->isDirty() || !tile->frontTexture()) tile->reserveTexture(); bool hasPicture = m_paintingPicture != 0; // safely read on UI thread, since only UI thread writes if (tile->backTexture() && tile->isDirty() && !tile->isRepaintPending() && hasPicture) { PaintTileOperation *operation = new PaintTileOperation(tile, m_surface); TilesManager::instance()->scheduleOperation(operation); } } BaseTile* TiledTexture::getTile(int x, int y) { for (unsigned int i = 0; i x() == x && tile->y() == y) return tile; } return 0; } int TiledTexture::nbTextures(IntRect& area, float scale) { IntRect tileBounds = computeTilesArea(area, scale); int numberTextures = tileBounds.width() * tileBounds.height(); // add the number of dirty tiles in the bounds, as they take up double // textures for double buffering for (unsigned int i = 0; i isDirty() && tile->x() >= tileBounds.x() && tile->x() <= tileBounds.maxX() && tile->y() >= tileBounds.y() && tile->y() <= tileBounds.maxY()) numberTextures++; } return numberTextures; } bool TiledTexture::draw(IntRect& visibleArea) { if (!m_surface) return true; XLOG("TT %p draw", this); #ifdef DEBUG TilesManager::instance()->getTilesTracker()->trackLayer(); #endif m_area = computeTilesArea(visibleArea, m_scale); if (m_area.width() == 0 || m_area.height() == 0) return false; #ifdef DEBUG TilesManager::instance()->getTilesTracker()->trackVisibleLayer(); #endif float m_invScale = 1 / m_scale; const float tileWidth = TilesManager::layerTileWidth() * m_invScale; const float tileHeight = TilesManager::layerTileHeight() * m_invScale; bool askRedraw = false; for (unsigned int i = 0; i < m_tiles.size(); i++) { BaseTile* tile = m_tiles[i]; if (tile->isTileVisible(m_area)) { askRedraw |= !tile->isTileReady(); SkRect rect; rect.fLeft = tile->x() * tileWidth; rect.fTop = tile->y() * tileHeight; rect.fRight = rect.fLeft + tileWidth; rect.fBottom = rect.fTop + tileHeight; XLOG("- [%d], { painter %x vs %x }, tile %x (layer tile: %d) %d,%d at scale %.2f vs %.2f [ready: %d] dirty: %d", i, this, tile->painter(), tile, tile->isLayerTile(), tile->x(), tile->y(), tile->scale(), m_scale, tile->isTileReady(), tile->isDirty()); tile->draw(m_surface->opacity(), rect, m_scale); #ifdef DEBUG TilesManager::instance()->getTilesTracker()->track(tile->isTileReady(), tile->backTexture()); #endif } } // need to redraw if some visible tile wasn't ready return askRedraw; } bool TiledTexture::paint(BaseTile* tile, SkCanvas* canvas, unsigned int* pictureUsed) { m_paintingPictureSync.lock(); SkPicture* picture = m_paintingPicture; SkSafeRef(picture); m_paintingPictureSync.unlock(); if (!picture) { XLOG("TT %p COULDNT PAINT, NO PICTURE", this); return false; } XLOG("TT %p painting tile %d, %d with picture %p", this, tile->x(), tile->y(), picture); canvas->drawPicture(*picture); SkSafeUnref(picture); return true; } const TransformationMatrix* TiledTexture::transform() { if (!m_surface) return 0; return m_surface->transform(); } void TiledTexture::removeTiles() { for (unsigned int i = 0; i < m_tiles.size(); i++) { delete m_tiles[i]; } m_tiles.clear(); } void TiledTexture::discardTextures() { for (unsigned int i = 0; i < m_tiles.size(); i++) m_tiles[i]->discardTextures(); } bool TiledTexture::owns(BaseTileTexture* texture) { for (unsigned int i = 0; i < m_tiles.size(); i++) { BaseTile* tile = m_tiles[i]; if (tile->frontTexture() == texture) return true; if (tile->backTexture() == texture) return true; } return false; } DualTiledTexture::DualTiledTexture(SurfacePainter* surface) { m_textureA = new TiledTexture(surface); m_textureB = new TiledTexture(surface); m_frontTexture = m_textureA; m_backTexture = m_textureB; m_scale = -1; m_futureScale = -1; m_zooming = false; } DualTiledTexture::~DualTiledTexture() { delete m_textureA; delete m_textureB; } void DualTiledTexture::prepare(GLWebViewState* state, float scale, bool repaint, bool startFastSwap, IntRect& prepareArea) { // If we are zooming, we will use the previously used area, to prevent the // frontTexture to try to allocate more tiles than what it has already if (!m_zooming) m_preZoomPrepareArea = prepareArea; if (m_futureScale != scale) { m_futureScale = scale; m_zoomUpdateTime = WTF::currentTime() + DualTiledTexture::s_zoomUpdateDelay; m_zooming = true; } XLOG("Preparing DTT %p with scale %.2f, m_scale %.2f, futureScale: %.2f, zooming: %d", this, scale, m_scale, m_futureScale, m_zooming); if (m_scale > 0) m_frontTexture->prepare(state, m_scale, repaint, startFastSwap, m_preZoomPrepareArea); // If we had a scheduled update if (m_zooming && m_zoomUpdateTime < WTF::currentTime()) { m_backTexture->prepare(state, m_futureScale, repaint, startFastSwap, prepareArea); if (m_backTexture->ready()) { m_backTexture->swapTiles(); swap(); m_zooming = false; } } } void DualTiledTexture::swap() { m_frontTexture = m_frontTexture == m_textureA ? m_textureB : m_textureA; m_backTexture = m_backTexture == m_textureA ? m_textureB : m_textureA; m_scale = m_futureScale; m_backTexture->discardTextures(); } bool DualTiledTexture::draw(IntRect& visibleArea) { bool needsRepaint = m_frontTexture->draw(visibleArea); needsRepaint |= m_zooming; needsRepaint |= (m_scale <= 0); return needsRepaint; } void DualTiledTexture::update(const SkRegion& dirtyArea, SkPicture* picture) { m_backTexture->update(dirtyArea, picture); m_frontTexture->update(dirtyArea, picture); } void DualTiledTexture::swapTiles() { m_backTexture->swapTiles(); m_frontTexture->swapTiles(); } bool DualTiledTexture::owns(BaseTileTexture* texture) { bool owns = m_textureA->owns(texture); owns |= m_textureB->owns(texture); return owns; } } // namespace WebCore