/* * 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 #include #include #include #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(tileWidth); finalRealRect.fTop = finalRealRect.fTop % static_cast(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)