diff options
Diffstat (limited to 'Source/WebCore/platform/graphics/android/Tile.cpp')
-rw-r--r-- | Source/WebCore/platform/graphics/android/Tile.cpp | 538 |
1 files changed, 538 insertions, 0 deletions
diff --git a/Source/WebCore/platform/graphics/android/Tile.cpp b/Source/WebCore/platform/graphics/android/Tile.cpp new file mode 100644 index 0000000..1612337 --- /dev/null +++ b/Source/WebCore/platform/graphics/android/Tile.cpp @@ -0,0 +1,538 @@ +/* + * 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. + */ + +#define LOG_TAG "Tile" +#define LOG_NDEBUG 1 + +#include "config.h" +#include "Tile.h" + +#if USE(ACCELERATED_COMPOSITING) + +#include "AndroidLog.h" +#include "GLUtils.h" +#include "RasterRenderer.h" +#include "TextureInfo.h" +#include "TileTexture.h" +#include "TilesManager.h" + +// If the dirty portion of a tile exceeds this ratio, fully repaint. +// Lower values give fewer partial repaints, thus fewer front-to-back +// texture copies (cost will vary by device). It's a tradeoff between +// the rasterization cost and the FBO texture recopy cost when using +// GPU for the transfer queue. +#define MAX_INVAL_AREA 0.6 + +namespace WebCore { + +Tile::Tile(bool isLayerTile) + : m_x(-1) + , m_y(-1) + , m_frontTexture(0) + , m_backTexture(0) + , m_scale(1) + , m_dirty(true) + , m_repaintPending(false) + , m_fullRepaint(true) + , m_isTexturePainted(false) + , m_isLayerTile(isLayerTile) + , m_drawCount(0) + , m_state(Unpainted) +{ +#ifdef DEBUG_COUNT + ClassTracker::instance()->increment("Tile"); +#endif + m_renderer = BaseRenderer::createRenderer(); +} + +Tile::~Tile() +{ + if (m_backTexture) + m_backTexture->release(this); + if (m_frontTexture) + m_frontTexture->release(this); + + delete m_renderer; + +#ifdef DEBUG_COUNT + ClassTracker::instance()->decrement("Tile"); +#endif +} + +// All the following functions must be called from the main GL thread. + +void Tile::setContents(int x, int y, float scale, bool isExpandedPrefetchTile) +{ + // TODO: investigate whether below check/discard is necessary + if ((m_x != x) + || (m_y != y) + || (m_scale != scale)) { + // neither texture is relevant + discardTextures(); + } + + android::AutoMutex lock(m_atomicSync); + m_x = x; + m_y = y; + m_scale = scale; + m_drawCount = TilesManager::instance()->getDrawGLCount(); + if (isExpandedPrefetchTile) + m_drawCount--; // deprioritize expanded painting region +} + +void Tile::reserveTexture() +{ + TileTexture* texture = TilesManager::instance()->getAvailableTexture(this); + + android::AutoMutex lock(m_atomicSync); + if (texture && m_backTexture != texture) { + ALOGV("tile %p reserving texture %p, back was %p (front %p)", + this, texture, m_backTexture, m_frontTexture); + m_state = Unpainted; + m_backTexture = texture; + } + + if (m_state == UpToDate) { + ALOGV("moving tile %p to unpainted, since it reserved while up to date", this); + m_dirty = true; + m_state = Unpainted; + } +} + +bool Tile::removeTexture(TileTexture* texture) +{ + ALOGV("%p removeTexture %p, back %p front %p... page %p", + this, texture, m_backTexture, m_frontTexture, m_page); + // We update atomically, so paintBitmap() can see the correct value + android::AutoMutex lock(m_atomicSync); + if (m_frontTexture == texture) { + if (m_state == UpToDate) { + ALOGV("front texture removed, state was UpToDate, now becoming unpainted, bt is %p", m_backTexture); + m_state = Unpainted; + } + + m_frontTexture = 0; + } + if (m_backTexture == texture) { + m_state = Unpainted; + m_backTexture = 0; + } + + // mark dirty regardless of which texture was taken - the back texture may + // have been ready to swap + m_dirty = true; + + return true; +} + +void Tile::markAsDirty(const SkRegion& dirtyArea) +{ + if (dirtyArea.isEmpty()) + return; + android::AutoMutex lock(m_atomicSync); + m_dirtyArea.op(dirtyArea, SkRegion::kUnion_Op); + + // Check if we actually intersect with the area + bool intersect = false; + SkRegion::Iterator cliperator(dirtyArea); + SkRect realTileRect; + SkRect dirtyRect; + while (!cliperator.done()) { + dirtyRect.set(cliperator.rect()); + if (intersectWithRect(m_x, m_y, TilesManager::tileWidth(), TilesManager::tileHeight(), + m_scale, dirtyRect, realTileRect)) { + intersect = true; + break; + } + cliperator.next(); + } + + if (!intersect) + return; + + m_dirty = true; + if (m_state == UpToDate) { + // We only mark a tile as unpainted in 'markAsDirty' if its status is + // UpToDate: marking dirty means we need to repaint, but don't stop the + // current paint + m_state = Unpainted; + } else if (m_state != Unpainted) { + // TODO: fix it so that they can paint while deferring the markAsDirty + // call (or block updates) + ALOGV("Warning: tried to mark tile %p at %d, %d islayertile %d as dirty, state %d, page %p", + this, m_x, m_y, isLayerTile(), m_state, m_page); + + // prefetch tiles can be marked dirty while in the process of painting, + // due to not using an update lock. force them to fail validate step. + m_state = Unpainted; + } +} + +bool Tile::isDirty() +{ + android::AutoMutex lock(m_atomicSync); + return m_dirty; +} + +bool Tile::isRepaintPending() +{ + android::AutoMutex lock(m_atomicSync); + return m_repaintPending; +} + +void Tile::setRepaintPending(bool pending) +{ + android::AutoMutex lock(m_atomicSync); + m_repaintPending = pending; +} + +bool Tile::drawGL(float opacity, const SkRect& rect, float scale, + const TransformationMatrix* transform) +{ + if (m_x < 0 || m_y < 0 || m_scale != scale) + return false; + + // No need to mutex protect reads of m_backTexture as it is only written to by + // the consumer thread. + if (!m_frontTexture) + return false; + + // Early return if set to un-usable in purpose! + m_atomicSync.lock(); + bool isTexturePainted = m_isTexturePainted; + m_atomicSync.unlock(); + + if (!isTexturePainted) + return false; + + m_frontTexture->drawGL(isLayerTile(), rect, opacity, transform); + return true; +} + +bool Tile::isTileReady() +{ + // Return true if the tile's most recently drawn texture is up to date + android::AutoMutex lock(m_atomicSync); + TileTexture * texture = (m_state == ReadyToSwap) ? m_backTexture : m_frontTexture; + + if (!texture) + return false; + + if (texture->owner() != this) + return false; + + if (m_dirty) + return false; + + if (m_state != ReadyToSwap && m_state != UpToDate) + return false; + + return true; +} + +bool Tile::intersectWithRect(int x, int y, int tileWidth, int tileHeight, + float scale, const SkRect& dirtyRect, + SkRect& realTileRect) +{ + // compute the rect to corresponds to pixels + realTileRect.fLeft = x * tileWidth; + realTileRect.fTop = y * tileHeight; + realTileRect.fRight = realTileRect.fLeft + tileWidth; + realTileRect.fBottom = realTileRect.fTop + tileHeight; + + // scale the dirtyRect for intersect computation. + SkRect realDirtyRect = SkRect::MakeWH(dirtyRect.width() * scale, + dirtyRect.height() * scale); + realDirtyRect.offset(dirtyRect.fLeft * scale, dirtyRect.fTop * scale); + + if (!realTileRect.intersect(realDirtyRect)) + return false; + return true; +} + +bool Tile::isTileVisible(const IntRect& viewTileBounds) +{ + return (m_x >= viewTileBounds.x() + && m_x < viewTileBounds.x() + viewTileBounds.width() + && m_y >= viewTileBounds.y() + && m_y < viewTileBounds.y() + viewTileBounds.height()); +} + +// This is called from the texture generation thread +void Tile::paintBitmap(TilePainter* painter) +{ + // We acquire the values below atomically. This ensures that we are reading + // values correctly across cores. Further, once we have these values they + // can be updated by other threads without consequence. + m_atomicSync.lock(); + bool dirty = m_dirty; + TileTexture* texture = m_backTexture; + SkRegion dirtyArea = m_dirtyArea; + float scale = m_scale; + const int x = m_x; + const int y = m_y; + + if (!dirty || !texture) { + m_atomicSync.unlock(); + return; + } + if (m_state != Unpainted) { + ALOGV("Warning: started painting tile %p, but was at state %d, ft %p bt %p", + this, m_state, m_frontTexture, m_backTexture); + } + m_state = PaintingStarted; + TextureInfo* textureInfo = texture->getTextureInfo(); + m_atomicSync.unlock(); + + // at this point we can safely check the ownership (if the texture got + // transferred to another Tile under us) + if (texture->owner() != this) { + return; + } + + // swap out the renderer if necessary + BaseRenderer::swapRendererIfNeeded(m_renderer); + // setup the common renderInfo fields; + TileRenderInfo renderInfo; + renderInfo.x = x; + renderInfo.y = y; + renderInfo.scale = scale; + renderInfo.tileSize = texture->getSize(); + renderInfo.tilePainter = painter; + renderInfo.baseTile = this; + renderInfo.textureInfo = textureInfo; + + const float tileWidth = renderInfo.tileSize.width(); + const float tileHeight = renderInfo.tileSize.height(); + + SkRegion::Iterator cliperator(dirtyArea); + + bool fullRepaint = false; + + if (m_fullRepaint + || textureInfo->m_width != tileWidth + || textureInfo->m_height != tileHeight) { + fullRepaint = true; + } + + // For now, only do full repaint + fullRepaint = true; + + if (!fullRepaint) { + // compute the partial inval area + SkIRect totalRect; + totalRect.set(0, 0, 0, 0); + float tileSurface = tileWidth * tileHeight; + float tileSurfaceCap = MAX_INVAL_AREA * tileSurface; + + // We join all the invals in the same tile for now + while (!fullRepaint && !cliperator.done()) { + SkRect realTileRect; + SkRect dirtyRect; + dirtyRect.set(cliperator.rect()); + bool intersect = intersectWithRect(x, y, tileWidth, tileHeight, + scale, dirtyRect, realTileRect); + if (intersect) { + // initialize finalRealRect to the rounded values of realTileRect + SkIRect finalRealRect; + realTileRect.roundOut(&finalRealRect); + + // stash the int values of the current width and height + const int iWidth = finalRealRect.width(); + const int iHeight = finalRealRect.height(); + + if (iWidth == tileWidth || iHeight == tileHeight) { + fullRepaint = true; + break; + } + + // translate the rect into tile space coordinates + finalRealRect.fLeft = finalRealRect.fLeft % static_cast<int>(tileWidth); + finalRealRect.fTop = finalRealRect.fTop % static_cast<int>(tileHeight); + finalRealRect.fRight = finalRealRect.fLeft + iWidth; + finalRealRect.fBottom = finalRealRect.fTop + iHeight; + totalRect.join(finalRealRect); + float repaintSurface = totalRect.width() * totalRect.height(); + + if (repaintSurface > tileSurfaceCap) { + fullRepaint = true; + break; + } + } + + cliperator.next(); + } + + if (!fullRepaint) { + renderInfo.invalRect = &totalRect; + m_renderer->renderTiledContent(renderInfo); + } + } + + // Do a full repaint if needed + if (fullRepaint) { + renderInfo.invalRect = 0; + m_renderer->renderTiledContent(renderInfo); + } + + m_atomicSync.lock(); + + if (texture == m_backTexture) { + m_isTexturePainted = true; + + // set the fullrepaint flags + m_fullRepaint = false; + + // The various checks to see if we are still dirty... + + m_dirty = false; + + if (m_scale != scale) + m_dirty = true; + + if (fullRepaint) + m_dirtyArea.setEmpty(); + else + m_dirtyArea.op(dirtyArea, SkRegion::kDifference_Op); + + if (!m_dirtyArea.isEmpty()) + m_dirty = true; + + ALOGV("painted tile %p (%d, %d), texture %p, dirty=%d", this, x, y, texture, m_dirty); + + validatePaint(); + } else { + ALOGV("tile %p no longer owns texture %p, m_state %d. ft %p bt %p", + this, texture, m_state, m_frontTexture, m_backTexture); + } + + m_atomicSync.unlock(); +} + +void Tile::discardTextures() { + android::AutoMutex lock(m_atomicSync); + ALOGV("%p discarding bt %p, ft %p", + this, m_backTexture, m_frontTexture); + if (m_frontTexture) { + m_frontTexture->release(this); + m_frontTexture = 0; + } + if (m_backTexture) { + m_backTexture->release(this); + m_backTexture = 0; + } + m_dirtyArea.setEmpty(); + m_fullRepaint = true; + + m_dirty = true; + m_state = Unpainted; +} + +void Tile::discardBackTexture() { + android::AutoMutex lock(m_atomicSync); + if (m_backTexture) { + m_backTexture->release(this); + m_backTexture = 0; + } + m_state = Unpainted; + m_dirty = true; +} + +bool Tile::swapTexturesIfNeeded() { + android::AutoMutex lock(m_atomicSync); + if (m_state == ReadyToSwap) { + // discard old texture and swap the new one in its place + if (m_frontTexture) + m_frontTexture->release(this); + + m_frontTexture = m_backTexture; + m_backTexture = 0; + m_state = UpToDate; + ALOGV("display texture for %p at %d, %d front is now %p, back is %p", + this, m_x, m_y, m_frontTexture, m_backTexture); + + return true; + } + return false; +} + +void Tile::backTextureTransfer() { + android::AutoMutex lock(m_atomicSync); + if (m_state == PaintingStarted) + m_state = TransferredUnvalidated; + else if (m_state == ValidatedUntransferred) + m_state = ReadyToSwap; + else { + // shouldn't have transferred a tile in any other state, log + ALOGV("Note: transferred tile %p at %d %d, state wasn't paintingstarted or validated: %d", + this, m_x, m_y, m_state); + } +} + +void Tile::backTextureTransferFail() { + // transfer failed for some reason, mark dirty so it will (repaint and) be + // retransferred. + android::AutoMutex lock(m_atomicSync); + m_state = Unpainted; + m_dirty = true; + // whether validatePaint is called before or after, it won't do anything +} + +void Tile::validatePaint() { + // ONLY CALL while m_atomicSync is locked (at the end of paintBitmap()) + + if (!m_dirty) { + // since after the paint, the tile isn't dirty, 'validate' it - this + // may happed before or after the transfer queue operation. Only + // when both have happened, mark as 'ReadyToSwap' + if (m_state == PaintingStarted) + m_state = ValidatedUntransferred; + else if (m_state == TransferredUnvalidated) { + // When the backTexture has been marked pureColor, we will skip the + // transfer and marked as ReadyToSwap, in this case, we don't want + // to reset m_dirty bit to true. + m_state = ReadyToSwap; + } else { + ALOGV("Note: validated tile %p at %d %d, state wasn't paintingstarted or transferred %d", + this, m_x, m_y, m_state); + // failed transferring, in which case mark dirty (since + // paintBitmap() may have cleared m_dirty) + m_dirty = true; + } + + if (m_deferredDirty) { + ALOGV("Note: deferred dirty flag set, possibly a missed paint on tile %p", this); + m_deferredDirty = false; + } + } else { + ALOGV("Note: paint was unsuccessful."); + m_state = Unpainted; + } + +} + +} // namespace WebCore + +#endif // USE(ACCELERATED_COMPOSITING) |