| /* |
| * 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 "TilesManager" |
| #define LOG_NDEBUG 1 |
| |
| #include "config.h" |
| #include "TilesManager.h" |
| |
| #if USE(ACCELERATED_COMPOSITING) |
| |
| #include "AndroidLog.h" |
| #include "GLWebViewState.h" |
| #include "SkCanvas.h" |
| #include "SkDevice.h" |
| #include "SkPaint.h" |
| #include "Tile.h" |
| #include "TileTexture.h" |
| #include "TransferQueue.h" |
| |
| #include <android/native_window.h> |
| #include <cutils/atomic.h> |
| #include <gui/GLConsumer.h> |
| #include <gui/Surface.h> |
| #include <wtf/CurrentTime.h> |
| |
| // Important: We need at least twice as many textures as is needed to cover |
| // one viewport, otherwise the allocation may stall. |
| // We need n textures for one TiledPage, and another n textures for the |
| // second page used when scaling. |
| // In our case, we use 256*256 textures. Both base and layers can use up to |
| // MAX_TEXTURE_ALLOCATION textures, which is 224MB GPU memory in total. |
| // For low end graphics systems, we cut this upper limit to half. |
| // We've found the viewport dependent value m_currentTextureCount is a reasonable |
| // number to cap the layer tile texturs, it worked on both phones and tablets. |
| // TODO: after merge the pool of base tiles and layer tiles, we should revisit |
| // the logic of allocation management. |
| #define MAX_TEXTURE_ALLOCATION ((10+TILE_PREFETCH_DISTANCE*2)*(7+TILE_PREFETCH_DISTANCE*2)*4) |
| #define TILE_WIDTH 256 |
| #define TILE_HEIGHT 256 |
| |
| #define BYTES_PER_PIXEL 4 // 8888 config |
| |
| #define LAYER_TEXTURES_DESTROY_TIMEOUT 60 // If we do not need layers for 60 seconds, free the textures |
| |
| // Eventually this should be dynamically be determined, and smart scheduling |
| // between the generators should be implemented |
| #define NUM_TEXTURES_GENERATORS 1 |
| |
| namespace WebCore { |
| |
| int TilesManager::getMaxTextureAllocation() |
| { |
| if (m_maxTextureAllocation == -1) { |
| GLint glMaxTextureSize = 0; |
| glGetIntegerv(GL_MAX_TEXTURE_SIZE, &glMaxTextureSize); |
| GLUtils::checkGlError("TilesManager::getMaxTextureAllocation"); |
| // Half of glMaxTextureSize can be used for base, the other half for layers. |
| m_maxTextureAllocation = std::min(MAX_TEXTURE_ALLOCATION, glMaxTextureSize / 2); |
| if (!m_highEndGfx) |
| m_maxTextureAllocation = m_maxTextureAllocation / 2; |
| } |
| return m_maxTextureAllocation; |
| } |
| |
| TilesManager::TilesManager() |
| : m_layerTexturesRemain(true) |
| , m_highEndGfx(false) |
| , m_currentTextureCount(0) |
| , m_currentLayerTextureCount(0) |
| , m_maxTextureAllocation(-1) |
| , m_generatorReady(false) |
| , m_showVisualIndicator(false) |
| , m_invertedScreen(false) |
| , m_useMinimalMemory(true) |
| , m_useDoubleBuffering(true) |
| , m_contentUpdates(0) |
| , m_webkitContentUpdates(0) |
| , m_queue(0) |
| , m_drawGLCount(1) |
| , m_lastTimeLayersUsed(0) |
| , m_hasLayerTextures(false) |
| , m_eglContext(EGL_NO_CONTEXT) |
| { |
| ALOGV("TilesManager ctor"); |
| m_textures.reserveCapacity(MAX_TEXTURE_ALLOCATION / 2); |
| m_availableTextures.reserveCapacity(MAX_TEXTURE_ALLOCATION / 2); |
| m_tilesTextures.reserveCapacity(MAX_TEXTURE_ALLOCATION / 2); |
| m_availableTilesTextures.reserveCapacity(MAX_TEXTURE_ALLOCATION / 2); |
| |
| m_textureGenerators = new sp<TexturesGenerator>[NUM_TEXTURES_GENERATORS]; |
| for (int i = 0; i < NUM_TEXTURES_GENERATORS; i++) { |
| m_textureGenerators[i] = new TexturesGenerator(this); |
| ALOGD("Starting TG #%d, %p", i, m_textureGenerators[i].get()); |
| m_textureGenerators[i]->run("TexturesGenerator"); |
| } |
| } |
| |
| TilesManager::~TilesManager() |
| { |
| delete[] m_textureGenerators; |
| } |
| |
| |
| void TilesManager::allocateTextures() |
| { |
| int nbTexturesToAllocate = m_currentTextureCount - m_textures.size(); |
| ALOGV("%d tiles to allocate (%d textures planned)", nbTexturesToAllocate, m_currentTextureCount); |
| int nbTexturesAllocated = 0; |
| for (int i = 0; i < nbTexturesToAllocate; i++) { |
| TileTexture* texture = new TileTexture( |
| tileWidth(), tileHeight()); |
| // the atomic load ensures that the texture has been fully initialized |
| // before we pass a pointer for other threads to operate on |
| TileTexture* loadedTexture = |
| reinterpret_cast<TileTexture*>( |
| android_atomic_acquire_load(reinterpret_cast<int32_t*>(&texture))); |
| m_textures.append(loadedTexture); |
| nbTexturesAllocated++; |
| } |
| |
| int nbLayersTexturesToAllocate = m_currentLayerTextureCount - m_tilesTextures.size(); |
| ALOGV("%d layers tiles to allocate (%d textures planned)", |
| nbLayersTexturesToAllocate, m_currentLayerTextureCount); |
| int nbLayersTexturesAllocated = 0; |
| for (int i = 0; i < nbLayersTexturesToAllocate; i++) { |
| TileTexture* texture = new TileTexture( |
| tileWidth(), tileHeight()); |
| // the atomic load ensures that the texture has been fully initialized |
| // before we pass a pointer for other threads to operate on |
| TileTexture* loadedTexture = |
| reinterpret_cast<TileTexture*>( |
| android_atomic_acquire_load(reinterpret_cast<int32_t*>(&texture))); |
| m_tilesTextures.append(loadedTexture); |
| nbLayersTexturesAllocated++; |
| } |
| ALOGV("allocated %d textures for base (total: %d, %d Mb), %d textures for layers (total: %d, %d Mb)", |
| nbTexturesAllocated, m_textures.size(), |
| m_textures.size() * TILE_WIDTH * TILE_HEIGHT * 4 / 1024 / 1024, |
| nbLayersTexturesAllocated, m_tilesTextures.size(), |
| m_tilesTextures.size() * tileWidth() * tileHeight() * 4 / 1024 / 1024); |
| } |
| |
| void TilesManager::discardTextures(bool allTextures, bool glTextures) |
| { |
| const unsigned int max = m_textures.size(); |
| |
| unsigned long long sparedDrawCount = ~0; // by default, spare no textures |
| if (!allTextures) { |
| // if we're not deallocating all textures, spare those with max drawcount |
| sparedDrawCount = 0; |
| for (unsigned int i = 0; i < max; i++) { |
| TextureOwner* owner = m_textures[i]->owner(); |
| if (owner) |
| sparedDrawCount = std::max(sparedDrawCount, owner->drawCount()); |
| } |
| } |
| discardTexturesVector(sparedDrawCount, m_textures, glTextures); |
| discardTexturesVector(sparedDrawCount, m_tilesTextures, glTextures); |
| } |
| |
| void TilesManager::markAllGLTexturesZero() |
| { |
| for (unsigned int i = 0; i < m_textures.size(); i++) |
| m_textures[i]->m_ownTextureId = 0; |
| for (unsigned int i = 0; i < m_tilesTextures.size(); i++) |
| m_tilesTextures[i]->m_ownTextureId = 0; |
| } |
| |
| void TilesManager::discardTexturesVector(unsigned long long sparedDrawCount, |
| WTF::Vector<TileTexture*>& textures, |
| bool deallocateGLTextures) |
| { |
| const unsigned int max = textures.size(); |
| int dealloc = 0; |
| WTF::Vector<int> discardedIndex; |
| for (unsigned int i = 0; i < max; i++) { |
| TextureOwner* owner = textures[i]->owner(); |
| if (!owner || owner->drawCount() < sparedDrawCount) { |
| if (deallocateGLTextures) { |
| // deallocate textures' gl memory |
| textures[i]->discardGLTexture(); |
| discardedIndex.append(i); |
| } else if (owner) { |
| // simply detach textures from owner |
| static_cast<Tile*>(owner)->discardTextures(); |
| } |
| dealloc++; |
| } |
| } |
| |
| bool base = textures == m_textures; |
| // Clean up the vector of TileTextures and reset the max texture count. |
| if (discardedIndex.size()) { |
| android::Mutex::Autolock lock(m_texturesLock); |
| for (int i = discardedIndex.size() - 1; i >= 0; i--) |
| textures.remove(discardedIndex[i]); |
| |
| int remainedTextureNumber = textures.size(); |
| int* countPtr = base ? &m_currentTextureCount : &m_currentLayerTextureCount; |
| if (remainedTextureNumber < *countPtr) { |
| ALOGV("reset currentTextureCount for %s tiles from %d to %d", |
| base ? "base" : "layer", *countPtr, remainedTextureNumber); |
| *countPtr = remainedTextureNumber; |
| } |
| |
| } |
| |
| ALOGV("Discarded %d %s textures (out of %d %s tiles)", |
| dealloc, (deallocateGLTextures ? "gl" : ""), |
| max, base ? "base" : "layer"); |
| } |
| |
| void TilesManager::gatherTexturesNumbers(int* nbTextures, int* nbAllocatedTextures, |
| int* nbLayerTextures, int* nbAllocatedLayerTextures) |
| { |
| *nbTextures = m_textures.size(); |
| for (unsigned int i = 0; i < m_textures.size(); i++) { |
| TileTexture* texture = m_textures[i]; |
| if (texture->m_ownTextureId) |
| *nbAllocatedTextures += 1; |
| } |
| *nbLayerTextures = m_tilesTextures.size(); |
| for (unsigned int i = 0; i < m_tilesTextures.size(); i++) { |
| TileTexture* texture = m_tilesTextures[i]; |
| if (texture->m_ownTextureId) |
| *nbAllocatedLayerTextures += 1; |
| } |
| } |
| |
| void TilesManager::dirtyTexturesVector(WTF::Vector<TileTexture*>& textures) |
| { |
| for (unsigned int i = 0; i < textures.size(); i++) { |
| Tile* currentOwner = static_cast<Tile*>(textures[i]->owner()); |
| if (currentOwner) |
| currentOwner->markAsDirty(); |
| } |
| } |
| |
| void TilesManager::dirtyAllTiles() |
| { |
| dirtyTexturesVector(m_textures); |
| dirtyTexturesVector(m_tilesTextures); |
| } |
| |
| void TilesManager::printTextures() |
| { |
| #ifdef DEBUG |
| ALOGV("++++++"); |
| for (unsigned int i = 0; i < m_textures.size(); i++) { |
| TileTexture* texture = m_textures[i]; |
| Tile* o = 0; |
| if (texture->owner()) |
| o = (Tile*) texture->owner(); |
| int x = -1; |
| int y = -1; |
| if (o) { |
| x = o->x(); |
| y = o->y(); |
| } |
| ALOGV("[%d] texture %x owner: %x (%d, %d) scale: %.2f", |
| i, texture, o, x, y, o ? o->scale() : 0); |
| } |
| ALOGV("------"); |
| #endif // DEBUG |
| } |
| |
| void TilesManager::gatherTextures() |
| { |
| android::Mutex::Autolock lock(m_texturesLock); |
| m_availableTextures = m_textures; |
| m_availableTilesTextures = m_tilesTextures; |
| m_layerTexturesRemain = true; |
| } |
| |
| TileTexture* TilesManager::getAvailableTexture(Tile* owner) |
| { |
| android::Mutex::Autolock lock(m_texturesLock); |
| |
| WTF::Vector<TileTexture*>* availableTexturePool; |
| if (owner->isLayerTile()) |
| availableTexturePool = &m_availableTilesTextures; |
| else |
| availableTexturePool = &m_availableTextures; |
| |
| // Sanity check that the tile does not already own a texture |
| if (owner->backTexture() && owner->backTexture()->owner() == owner) { |
| int removeIndex = availableTexturePool->find(owner->backTexture()); |
| |
| // TODO: investigate why texture isn't found |
| if (removeIndex >= 0) |
| availableTexturePool->remove(removeIndex); |
| return owner->backTexture(); |
| } |
| |
| // The heuristic for selecting a texture is as follows: |
| // 1. Skip textures currently being painted, they can't be painted while |
| // busy anyway |
| // 2. If a tile isn't owned, break with that one |
| // 3. Don't let tiles acquire their front textures |
| // 4. Otherwise, use the least recently prepared tile, but ignoring tiles |
| // drawn in the last frame to avoid flickering |
| |
| TileTexture* farthestTexture = 0; |
| unsigned long long oldestDrawCount = getDrawGLCount() - 1; |
| const unsigned int max = availableTexturePool->size(); |
| for (unsigned int i = 0; i < max; i++) { |
| TileTexture* texture = (*availableTexturePool)[i]; |
| Tile* currentOwner = static_cast<Tile*>(texture->owner()); |
| if (!currentOwner) { |
| // unused texture! take it! |
| farthestTexture = texture; |
| break; |
| } |
| |
| if (currentOwner == owner) { |
| // Don't let a tile acquire its own front texture, as the |
| // acquisition logic doesn't handle that |
| continue; |
| } |
| |
| unsigned long long textureDrawCount = currentOwner->drawCount(); |
| if (oldestDrawCount > textureDrawCount) { |
| farthestTexture = texture; |
| oldestDrawCount = textureDrawCount; |
| } |
| } |
| |
| if (farthestTexture) { |
| Tile* previousOwner = static_cast<Tile*>(farthestTexture->owner()); |
| if (farthestTexture->acquire(owner)) { |
| if (previousOwner) { |
| previousOwner->removeTexture(farthestTexture); |
| |
| ALOGV("%s texture %p stolen from tile %d, %d for %d, %d, drawCount was %llu (now %llu)", |
| owner->isLayerTile() ? "LAYER" : "BASE", |
| farthestTexture, previousOwner->x(), previousOwner->y(), |
| owner->x(), owner->y(), |
| oldestDrawCount, getDrawGLCount()); |
| } |
| |
| availableTexturePool->remove(availableTexturePool->find(farthestTexture)); |
| return farthestTexture; |
| } |
| } else { |
| if (owner->isLayerTile()) { |
| // couldn't find a tile for a layer, layers shouldn't request redraw |
| // TODO: once we do layer prefetching, don't set this for those |
| // tiles |
| m_layerTexturesRemain = false; |
| } |
| } |
| |
| ALOGV("Couldn't find an available texture for %s tile %x (%d, %d) out of %d available", |
| owner->isLayerTile() ? "LAYER" : "BASE", |
| owner, owner->x(), owner->y(), max); |
| #ifdef DEBUG |
| printTextures(); |
| #endif // DEBUG |
| return 0; |
| } |
| |
| void TilesManager::setHighEndGfx(bool highEnd) |
| { |
| m_highEndGfx = highEnd; |
| } |
| |
| bool TilesManager::highEndGfx() |
| { |
| return m_highEndGfx; |
| } |
| |
| int TilesManager::currentTextureCount() |
| { |
| android::Mutex::Autolock lock(m_texturesLock); |
| return m_currentTextureCount; |
| } |
| |
| int TilesManager::currentLayerTextureCount() |
| { |
| android::Mutex::Autolock lock(m_texturesLock); |
| return m_currentLayerTextureCount; |
| } |
| |
| void TilesManager::setCurrentTextureCount(int newTextureCount) |
| { |
| int maxTextureAllocation = getMaxTextureAllocation(); |
| ALOGV("setCurrentTextureCount: %d (current: %d, max:%d)", |
| newTextureCount, m_currentTextureCount, maxTextureAllocation); |
| if (m_currentTextureCount == maxTextureAllocation || |
| newTextureCount <= m_currentTextureCount) |
| return; |
| |
| android::Mutex::Autolock lock(m_texturesLock); |
| m_currentTextureCount = std::min(newTextureCount, maxTextureAllocation); |
| |
| allocateTextures(); |
| } |
| |
| void TilesManager::setCurrentLayerTextureCount(int newTextureCount) |
| { |
| int maxTextureAllocation = getMaxTextureAllocation(); |
| ALOGV("setCurrentLayerTextureCount: %d (current: %d, max:%d)", |
| newTextureCount, m_currentLayerTextureCount, maxTextureAllocation); |
| if (!newTextureCount && m_hasLayerTextures) { |
| double secondsSinceLayersUsed = WTF::currentTime() - m_lastTimeLayersUsed; |
| if (secondsSinceLayersUsed > LAYER_TEXTURES_DESTROY_TIMEOUT) { |
| unsigned long long sparedDrawCount = ~0; // by default, spare no textures |
| bool deleteGLTextures = true; |
| discardTexturesVector(sparedDrawCount, m_tilesTextures, deleteGLTextures); |
| m_hasLayerTextures = false; |
| } |
| return; |
| } |
| m_lastTimeLayersUsed = WTF::currentTime(); |
| if (m_currentLayerTextureCount == maxTextureAllocation || |
| newTextureCount <= m_currentLayerTextureCount) |
| return; |
| |
| android::Mutex::Autolock lock(m_texturesLock); |
| m_currentLayerTextureCount = std::min(newTextureCount, maxTextureAllocation); |
| |
| allocateTextures(); |
| m_hasLayerTextures = true; |
| } |
| |
| TransferQueue* TilesManager::transferQueue() |
| { |
| // m_queue will be created on the UI thread, although it may |
| // be accessed from the TexturesGenerator. However, that can only happen after |
| // a previous transferQueue() call due to a prepare. |
| if (!m_queue) |
| m_queue = new TransferQueue(m_useMinimalMemory && !m_highEndGfx); |
| return m_queue; |
| } |
| |
| // When GL context changed or we get a low memory signal, we want to cleanup all |
| // the GPU memory webview is using. |
| // The recreation will be on the next incoming draw call at the drawGL of |
| // GLWebViewState or the VideoLayerAndroid |
| void TilesManager::cleanupGLResources() |
| { |
| transferQueue()->cleanupGLResourcesAndQueue(); |
| shader()->cleanupGLResources(); |
| videoLayerManager()->cleanupGLResources(); |
| m_eglContext = EGL_NO_CONTEXT; |
| GLUtils::checkGlError("TilesManager::cleanupGLResources"); |
| } |
| |
| void TilesManager::updateTilesIfContextVerified() |
| { |
| EGLContext ctx = eglGetCurrentContext(); |
| GLUtils::checkEglError("contextChanged"); |
| if (ctx != m_eglContext) { |
| if (m_eglContext != EGL_NO_CONTEXT) { |
| // A change in EGL context is an unexpected error, but we don't want to |
| // crash or ANR. Therefore, abandon the Surface Texture and GL resources; |
| // they'll be recreated later in setupDrawing. (We can't delete them |
| // since the context is gone) |
| ALOGE("Unexpected : EGLContext changed! current %x , expected %x", |
| ctx, m_eglContext); |
| transferQueue()->resetQueue(); |
| shader()->forceNeedsInit(); |
| videoLayerManager()->forceNeedsInit(); |
| markAllGLTexturesZero(); |
| } else { |
| // This is the first time we went into this new EGL context. |
| // We will have the GL resources to be re-inited and we can't update |
| // dirty tiles yet. |
| ALOGD("new EGLContext from framework: %x ", ctx); |
| } |
| } else { |
| // Here before we draw, update the Tile which has updated content. |
| // Inside this function, just do GPU blits from the transfer queue into |
| // the Tiles' texture. |
| transferQueue()->updateDirtyTiles(); |
| // Clean up GL textures for video layer. |
| videoLayerManager()->deleteUnusedTextures(); |
| } |
| m_eglContext = ctx; |
| return; |
| } |
| |
| void TilesManager::removeOperationsForFilter(OperationFilter* filter) |
| { |
| for (int i = 0; i < NUM_TEXTURES_GENERATORS; i++) |
| m_textureGenerators[i]->removeOperationsForFilter(filter); |
| delete filter; |
| } |
| |
| bool TilesManager::tryUpdateOperationWithPainter(Tile* tile, TilePainter* painter) |
| { |
| for (int i = 0; i < NUM_TEXTURES_GENERATORS; i++) { |
| if (m_textureGenerators[i]->tryUpdateOperationWithPainter(tile, painter)) |
| return true; |
| } |
| return false; |
| } |
| |
| void TilesManager::scheduleOperation(QueuedOperation* operation) |
| { |
| // TODO: painter awareness, store prefer awareness, store preferred thread into painter |
| m_scheduleThread = (m_scheduleThread + 1) % NUM_TEXTURES_GENERATORS; |
| m_textureGenerators[m_scheduleThread]->scheduleOperation(operation); |
| } |
| |
| int TilesManager::tileWidth() |
| { |
| return TILE_WIDTH; |
| } |
| |
| int TilesManager::tileHeight() |
| { |
| return TILE_HEIGHT; |
| } |
| |
| TilesManager* TilesManager::instance() |
| { |
| if (!gInstance) { |
| gInstance = new TilesManager(); |
| ALOGV("instance(), new gInstance is %x", gInstance); |
| } |
| return gInstance; |
| } |
| |
| TilesManager* TilesManager::gInstance = 0; |
| |
| } // namespace WebCore |
| |
| #endif // USE(ACCELERATED_COMPOSITING) |