blob: d5384166758750542ccf606639d98bf29e9eba5e [file] [log] [blame]
/*
* 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 <cutils/log.h>
#include <wtf/CurrentTime.h>
#include <wtf/text/CString.h>
#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& visibleArea, float scale)
{
IntRect computedArea;
IntRect area(visibleArea.x() * scale,
visibleArea.y() * scale,
ceilf(visibleArea.width() * scale),
ceilf(visibleArea.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& visibleArea)
{
if (!m_surface)
return;
// first, how many tiles do we need
m_area = computeTilesArea(visibleArea, scale);
if (m_area.isEmpty())
return;
XLOG("for TiledTexture %p, we prepare with scale %.2f, have a visible area of "
" %d, %d - %d x %d, corresponding to %d, %d x - %d x %d tiles",
this, scale,
visibleArea.x(), visibleArea.y(),
visibleArea.width(), visibleArea.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 <m_tiles.size(); i++) {
BaseTile* tile = m_tiles[i];
if (tile->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 <m_tiles.size(); i++) {
BaseTile* tile = m_tiles[i];
if (tile->isDirty()
&& tile->x() >= tileBounds.x() && tile->x() <= tileBounds.maxX()
&& tile->y() >= tileBounds.y() && tile->y() <= tileBounds.maxY())
numberTextures++;
}
return numberTextures;
}
bool TiledTexture::draw()
{
if (!m_surface)
return true;
XLOG("TT %p draw", this);
#ifdef DEBUG
TilesManager::instance()->getTilesTracker()->trackLayer();
#endif
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& visibleArea)
{
// 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_preZoomVisibleArea = visibleArea;
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_preZoomVisibleArea);
// If we had a scheduled update
if (m_zooming && m_zoomUpdateTime < WTF::currentTime()) {
m_backTexture->prepare(state, m_futureScale, repaint, startFastSwap, visibleArea);
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()
{
bool needsRepaint = m_frontTexture->draw();
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