blob: f44f79d14ea9d383c9de79845a316f7d3304ce75 [file] [log] [blame]
/*
* 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)