| /* |
| * 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 "BaseTile.h" |
| |
| #if USE(ACCELERATED_COMPOSITING) |
| |
| #include "GLUtils.h" |
| #include "RasterRenderer.h" |
| #include "TextureInfo.h" |
| #include "TilesManager.h" |
| |
| #include <cutils/atomic.h> |
| |
| #include <cutils/log.h> |
| #include <wtf/CurrentTime.h> |
| #include <wtf/text/CString.h> |
| |
| #undef XLOGC |
| #define XLOGC(...) android_printLog(ANDROID_LOG_DEBUG, "BaseTile", __VA_ARGS__) |
| |
| #ifdef DEBUG |
| |
| #undef XLOG |
| #define XLOG(...) android_printLog(ANDROID_LOG_DEBUG, "BaseTile", __VA_ARGS__) |
| |
| #else |
| |
| #undef XLOG |
| #define XLOG(...) |
| |
| #endif // DEBUG |
| |
| namespace WebCore { |
| |
| BaseTile::BaseTile(bool isLayerTile) |
| : m_glWebViewState(0) |
| , m_painter(0) |
| , m_x(-1) |
| , m_y(-1) |
| , m_page(0) |
| , m_frontTexture(0) |
| , m_backTexture(0) |
| , m_scale(1) |
| , m_dirty(true) |
| , m_repaintPending(false) |
| , m_lastDirtyPicture(0) |
| , m_isTexturePainted(false) |
| , m_isLayerTile(isLayerTile) |
| , m_drawCount(0) |
| , m_state(Unpainted) |
| { |
| #ifdef DEBUG_COUNT |
| ClassTracker::instance()->increment("BaseTile"); |
| #endif |
| m_currentDirtyAreaIndex = 0; |
| |
| // For EglImage Mode, the internal buffer should be 2. |
| // For Surface Texture mode, we only need one. |
| if (TilesManager::instance()->getSharedTextureMode() == EglImageMode) |
| m_maxBufferNumber = 2; |
| else |
| m_maxBufferNumber = 1; |
| |
| m_dirtyArea = new SkRegion[m_maxBufferNumber]; |
| m_fullRepaint = new bool[m_maxBufferNumber]; |
| for (int i = 0; i < m_maxBufferNumber; i++) |
| m_fullRepaint[i] = true; |
| |
| m_renderer = BaseRenderer::createRenderer(); |
| } |
| |
| BaseTile::~BaseTile() |
| { |
| if (m_backTexture) |
| m_backTexture->release(this); |
| if (m_frontTexture) |
| m_frontTexture->release(this); |
| |
| delete m_renderer; |
| delete[] m_dirtyArea; |
| delete[] m_fullRepaint; |
| |
| #ifdef DEBUG_COUNT |
| ClassTracker::instance()->decrement("BaseTile"); |
| #endif |
| } |
| |
| // All the following functions must be called from the main GL thread. |
| |
| void BaseTile::setContents(TilePainter* painter, int x, int y, float scale) |
| { |
| if ((m_painter != painter) |
| || (m_x != x) |
| || (m_y != y) |
| || (m_scale != scale)) { |
| // neither texture is relevant |
| discardTextures(); |
| } |
| |
| android::AutoMutex lock(m_atomicSync); |
| m_painter = painter; |
| m_x = x; |
| m_y = y; |
| m_scale = scale; |
| m_drawCount = TilesManager::instance()->getDrawGLCount(); |
| } |
| |
| void BaseTile::reserveTexture() |
| { |
| BaseTileTexture* texture = TilesManager::instance()->getAvailableTexture(this); |
| |
| android::AutoMutex lock(m_atomicSync); |
| if (texture && m_backTexture != texture) { |
| XLOG("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) { |
| XLOG("moving tile %p to unpainted, since it reserved while up to date", this); |
| m_dirty = true; |
| m_state = Unpainted; |
| } |
| } |
| |
| bool BaseTile::removeTexture(BaseTileTexture* texture) |
| { |
| XLOG("%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) { |
| XLOG("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 BaseTile::markAsDirty(int unsigned pictureCount, |
| const SkRegion& dirtyArea) |
| { |
| if (dirtyArea.isEmpty()) |
| return; |
| android::AutoMutex lock(m_atomicSync); |
| m_lastDirtyPicture = pictureCount; |
| for (int i = 0; i < m_maxBufferNumber; i++) |
| m_dirtyArea[i].op(dirtyArea, SkRegion::kUnion_Op); |
| |
| // Check if we actually intersect with the area |
| bool intersect = false; |
| SkRegion::Iterator cliperator(dirtyArea); |
| int tileWidth = TilesManager::instance()->tileWidth(); |
| int tileHeight = TilesManager::instance()->tileHeight(); |
| if (m_isLayerTile) { |
| tileWidth = TilesManager::instance()->layerTileWidth(); |
| tileHeight = TilesManager::instance()->layerTileHeight(); |
| } |
| SkRect realTileRect; |
| SkRect dirtyRect; |
| while (!cliperator.done()) { |
| dirtyRect.set(cliperator.rect()); |
| if (intersectWithRect(m_x, m_y, tileWidth, 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) |
| XLOG("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 BaseTile::isDirty() |
| { |
| android::AutoMutex lock(m_atomicSync); |
| return m_dirty; |
| } |
| |
| bool BaseTile::isRepaintPending() |
| { |
| android::AutoMutex lock(m_atomicSync); |
| return m_repaintPending; |
| } |
| |
| void BaseTile::setRepaintPending(bool pending) |
| { |
| android::AutoMutex lock(m_atomicSync); |
| m_repaintPending = pending; |
| } |
| |
| void BaseTile::draw(float transparency, SkRect& rect, float scale) |
| { |
| if (m_x < 0 || m_y < 0 || m_scale != scale) |
| return; |
| |
| // No need to mutex protect reads of m_backTexture as it is only written to by |
| // the consumer thread. |
| if (!m_frontTexture) |
| return; |
| |
| // Early return if set to un-usable in purpose! |
| m_atomicSync.lock(); |
| bool isTexturePainted = m_isTexturePainted; |
| m_atomicSync.unlock(); |
| |
| if (!isTexturePainted) |
| return; |
| |
| TextureInfo* textureInfo = m_frontTexture->consumerLock(); |
| if (!textureInfo) { |
| m_frontTexture->consumerRelease(); |
| return; |
| } |
| |
| if (m_frontTexture->readyFor(this)) { |
| if (isLayerTile() && m_painter && m_painter->transform()) |
| TilesManager::instance()->shader()->drawLayerQuad(*m_painter->transform(), |
| rect, m_frontTexture->m_ownTextureId, |
| transparency, true); |
| else |
| TilesManager::instance()->shader()->drawQuad(rect, m_frontTexture->m_ownTextureId, |
| transparency); |
| } else { |
| XLOG("tile %p at %d, %d not readyfor (at draw),", this, m_x, m_y); |
| } |
| |
| m_frontTexture->consumerRelease(); |
| } |
| |
| bool BaseTile::isTileReady() |
| { |
| // Return true if the tile's most recently drawn texture is up to date |
| android::AutoMutex lock(m_atomicSync); |
| BaseTileTexture * 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; |
| |
| texture->consumerLock(); |
| bool ready = texture->readyFor(this); |
| texture->consumerRelease(); |
| |
| if (ready) |
| return true; |
| |
| XLOG("tile %p at %d, %d not readyfor (at isTileReady)", this, m_x, m_y); |
| |
| return false; |
| } |
| |
| bool BaseTile::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 BaseTile::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 BaseTile::paintBitmap() |
| { |
| // 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; |
| BaseTileTexture* texture = m_backTexture; |
| SkRegion dirtyArea = m_dirtyArea[m_currentDirtyAreaIndex]; |
| float scale = m_scale; |
| const int x = m_x; |
| const int y = m_y; |
| TilePainter* painter = m_painter; |
| |
| if (!dirty || !texture) { |
| m_atomicSync.unlock(); |
| return; |
| } |
| if (m_state != Unpainted) { |
| XLOG("Warning: started painting tile %p, but was at state %d, ft %p bt %p", |
| this, m_state, m_frontTexture, m_backTexture); |
| } |
| m_state = PaintingStarted; |
| |
| texture->producerAcquireContext(); |
| TextureInfo* textureInfo = texture->producerLock(); |
| m_atomicSync.unlock(); |
| |
| // at this point we can safely check the ownership (if the texture got |
| // transferred to another BaseTile under us) |
| if (texture->owner() != this) { |
| texture->producerRelease(); |
| return; |
| } |
| |
| unsigned int pictureCount = 0; |
| |
| // 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[m_currentDirtyAreaIndex] |
| || textureInfo->m_width != tileWidth |
| || textureInfo->m_height != tileHeight) { |
| fullRepaint = true; |
| } |
| |
| bool surfaceTextureMode = textureInfo->getSharedTextureMode() == SurfaceTextureMode; |
| |
| if (surfaceTextureMode) |
| fullRepaint = true; |
| |
| while (!fullRepaint && !cliperator.done()) { |
| SkRect realTileRect; |
| SkRect dirtyRect; |
| dirtyRect.set(cliperator.rect()); |
| bool intersect = intersectWithRect(x, y, tileWidth, tileHeight, |
| scale, dirtyRect, realTileRect); |
| |
| // With SurfaceTexture, just repaint the entire tile if we intersect |
| // TODO: Implement the partial invalidate in Surface Texture Mode |
| if (intersect && surfaceTextureMode) { |
| fullRepaint = true; |
| break; |
| } |
| |
| if (intersect && !surfaceTextureMode) { |
| // 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; |
| |
| renderInfo.invalRect = &finalRealRect; |
| renderInfo.measurePerf = false; |
| |
| pictureCount = m_renderer->renderTiledContent(renderInfo); |
| } |
| |
| cliperator.next(); |
| } |
| |
| // Do a full repaint if needed |
| if (fullRepaint) { |
| SkIRect rect; |
| rect.set(0, 0, tileWidth, tileHeight); |
| |
| renderInfo.invalRect = ▭ |
| renderInfo.measurePerf = TilesManager::instance()->getShowVisualIndicator(); |
| |
| pictureCount = m_renderer->renderTiledContent(renderInfo); |
| } |
| |
| m_atomicSync.lock(); |
| |
| #if DEPRECATED_SURFACE_TEXTURE_MODE |
| texture->setTile(textureInfo, x, y, scale, painter, pictureCount); |
| #endif |
| texture->producerReleaseAndSwap(); |
| if (texture == m_backTexture) { |
| m_isTexturePainted = true; |
| |
| // set the fullrepaint flags |
| m_fullRepaint[m_currentDirtyAreaIndex] = 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[m_currentDirtyAreaIndex].setEmpty(); |
| else |
| m_dirtyArea[m_currentDirtyAreaIndex].op(dirtyArea, SkRegion::kDifference_Op); |
| |
| if (!m_dirtyArea[m_currentDirtyAreaIndex].isEmpty()) |
| m_dirty = true; |
| |
| // Now we can swap the dirty areas |
| // TODO: For surface texture in Async mode, the index will be updated |
| // according to the current buffer just dequeued. |
| m_currentDirtyAreaIndex = (m_currentDirtyAreaIndex+1) % m_maxBufferNumber; |
| |
| if (!m_dirtyArea[m_currentDirtyAreaIndex].isEmpty()) |
| m_dirty = true; |
| |
| XLOG("painted tile %p (%d, %d), texture %p, dirty=%d", this, x, y, texture, m_dirty); |
| |
| validatePaint(); |
| } else { |
| XLOG("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 BaseTile::discardTextures() { |
| android::AutoMutex lock(m_atomicSync); |
| XLOG("%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; |
| } |
| for (int i = 0; i < m_maxBufferNumber; i++) { |
| m_dirtyArea[i].setEmpty(); |
| m_fullRepaint[i] = true; |
| } |
| m_dirty = true; |
| m_state = Unpainted; |
| } |
| |
| void BaseTile::discardBackTexture() { |
| android::AutoMutex lock(m_atomicSync); |
| if (m_backTexture) { |
| m_backTexture->release(this); |
| m_backTexture = 0; |
| } |
| m_state = Unpainted; |
| m_dirty = true; |
| } |
| |
| bool BaseTile::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; |
| XLOG("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 BaseTile::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 |
| XLOG("Note: transferred tile %p at %d %d, state wasn't paintingstarted or validated: %d", |
| this, m_x, m_y, m_state); |
| } |
| } |
| |
| void BaseTile::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 BaseTile::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) |
| m_state = ReadyToSwap; |
| else { |
| XLOG("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) { |
| XLOG("Note: deferred dirty flag set, possibly a missed paint on tile %p", this); |
| m_deferredDirty = false; |
| } |
| } else { |
| XLOG("Note: paint was unsuccessful."); |
| m_state = Unpainted; |
| } |
| |
| } |
| |
| } // namespace WebCore |
| |
| #endif // USE(ACCELERATED_COMPOSITING) |