/* * 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 #include #include #include #include // 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[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( android_atomic_acquire_load(reinterpret_cast(&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( android_atomic_acquire_load(reinterpret_cast(&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& textures, bool deallocateGLTextures) { const unsigned int max = textures.size(); int dealloc = 0; WTF::Vector 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(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& textures) { for (unsigned int i = 0; i < textures.size(); i++) { Tile* currentOwner = static_cast(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* 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(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(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)