summaryrefslogtreecommitdiffstats
path: root/Source/WebCore/platform/graphics/android/Tile.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'Source/WebCore/platform/graphics/android/Tile.cpp')
-rw-r--r--Source/WebCore/platform/graphics/android/Tile.cpp538
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)