blob: 219435d6db48091e92b360d7559aa055247e4288 [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.
*/
#include "config.h"
#include "TilesManager.h"
#if USE(ACCELERATED_COMPOSITING)
#include "BaseTile.h"
#include "PaintedSurface.h"
#include "SkCanvas.h"
#include "SkDevice.h"
#include "SkPaint.h"
#include <android/native_window.h>
#include <cutils/atomic.h>
#include <gui/SurfaceTexture.h>
#include <gui/SurfaceTextureClient.h>
#include <cutils/log.h>
#include <wtf/CurrentTime.h>
#include <wtf/text/CString.h>
#undef XLOGC
#define XLOGC(...) android_printLog(ANDROID_LOG_DEBUG, "TilesManager", __VA_ARGS__)
#ifdef DEBUG
#undef XLOG
#define XLOG(...) android_printLog(ANDROID_LOG_DEBUG, "TilesManager", __VA_ARGS__)
#else
#undef XLOG
#define XLOG(...)
#endif // DEBUG
// 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. On the tablet, this equates to
// at least 60 textures, or 112 with expanded tile boundaries.
// 112(tiles)*256*256*4(bpp)*2(pages) = 56MB
// It turns out the viewport dependent value m_maxTextureCount 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 ((6+TILE_PREFETCH_DISTANCE*2)*(5+TILE_PREFETCH_DISTANCE*2)*4)
#define TILE_WIDTH 256
#define TILE_HEIGHT 256
#define LAYER_TILE_WIDTH 256
#define LAYER_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
namespace WebCore {
GLint TilesManager::getMaxTextureSize()
{
static GLint maxTextureSize = 0;
if (!maxTextureSize)
glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxTextureSize);
return maxTextureSize;
}
int TilesManager::getMaxTextureAllocation()
{
return MAX_TEXTURE_ALLOCATION;
}
TilesManager::TilesManager()
: m_layerTexturesRemain(true)
, m_maxTextureCount(0)
, m_maxLayerTextureCount(0)
, m_generatorReady(false)
, m_showVisualIndicator(false)
, m_invertedScreen(false)
, m_invertedScreenSwitch(false)
, m_useMinimalMemory(true)
, m_drawGLCount(1)
, m_lastTimeLayersUsed(0)
, m_hasLayerTextures(false)
{
XLOG("TilesManager ctor");
m_textures.reserveCapacity(MAX_TEXTURE_ALLOCATION);
m_availableTextures.reserveCapacity(MAX_TEXTURE_ALLOCATION);
m_tilesTextures.reserveCapacity(MAX_TEXTURE_ALLOCATION);
m_availableTilesTextures.reserveCapacity(MAX_TEXTURE_ALLOCATION);
m_pixmapsGenerationThread = new TexturesGenerator();
m_pixmapsGenerationThread->run("TexturesGenerator", android::PRIORITY_BACKGROUND);
}
void TilesManager::allocateTiles()
{
int nbTexturesToAllocate = m_maxTextureCount - m_textures.size();
XLOG("%d tiles to allocate (%d textures planned)", nbTexturesToAllocate, m_maxTextureCount);
int nbTexturesAllocated = 0;
for (int i = 0; i < nbTexturesToAllocate; i++) {
BaseTileTexture* texture = new BaseTileTexture(
tileWidth(), tileHeight());
// the atomic load ensures that the texture has been fully initialized
// before we pass a pointer for other threads to operate on
BaseTileTexture* loadedTexture =
reinterpret_cast<BaseTileTexture*>(
android_atomic_acquire_load(reinterpret_cast<int32_t*>(&texture)));
m_textures.append(loadedTexture);
nbTexturesAllocated++;
}
int nbLayersTexturesToAllocate = m_maxLayerTextureCount - m_tilesTextures.size();
XLOG("%d layers tiles to allocate (%d textures planned)",
nbLayersTexturesToAllocate, m_maxLayerTextureCount);
int nbLayersTexturesAllocated = 0;
for (int i = 0; i < nbLayersTexturesToAllocate; i++) {
BaseTileTexture* texture = new BaseTileTexture(
layerTileWidth(), layerTileHeight());
// the atomic load ensures that the texture has been fully initialized
// before we pass a pointer for other threads to operate on
BaseTileTexture* loadedTexture =
reinterpret_cast<BaseTileTexture*>(
android_atomic_acquire_load(reinterpret_cast<int32_t*>(&texture)));
m_tilesTextures.append(loadedTexture);
nbLayersTexturesAllocated++;
}
XLOG("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() * LAYER_TILE_WIDTH * LAYER_TILE_HEIGHT * 4 / 1024 / 1024);
}
void TilesManager::deallocateTextures(bool allTextures)
{
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());
}
}
deallocateTexturesVector(sparedDrawCount, m_textures);
deallocateTexturesVector(sparedDrawCount, m_tilesTextures);
}
void TilesManager::deallocateTexturesVector(unsigned long long sparedDrawCount,
WTF::Vector<BaseTileTexture*>& textures)
{
const unsigned int max = textures.size();
int dealloc = 0;
for (unsigned int i = 0; i < max; i++) {
TextureOwner* owner = textures[i]->owner();
if (!owner || owner->drawCount() < sparedDrawCount) {
textures[i]->discardGLTexture();
dealloc++;
}
}
XLOG("Deallocated %d gl textures (out of %d base tiles and %d layer tiles)",
dealloc, max, maxLayer);
}
void TilesManager::printTextures()
{
#ifdef DEBUG
XLOG("++++++");
for (unsigned int i = 0; i < m_textures.size(); i++) {
BaseTileTexture* texture = m_textures[i];
BaseTile* o = 0;
if (texture->owner())
o = (BaseTile*) texture->owner();
int x = -1;
int y = -1;
if (o) {
x = o->x();
y = o->y();
}
XLOG("[%d] texture %x busy: %d owner: %x (%d, %d) page: %x scale: %.2f",
i, texture,
texture->busy(), o, x, y, o ? o->page() : 0, o ? o->scale() : 0);
}
XLOG("------");
#endif // DEBUG
}
void TilesManager::addPaintedSurface(PaintedSurface* surface)
{
m_paintedSurfaces.append(surface);
}
void TilesManager::gatherTextures()
{
android::Mutex::Autolock lock(m_texturesLock);
m_availableTextures = m_textures;
}
void TilesManager::gatherLayerTextures()
{
android::Mutex::Autolock lock(m_texturesLock);
m_availableTilesTextures = m_tilesTextures;
m_layerTexturesRemain = true;
}
BaseTileTexture* TilesManager::getAvailableTexture(BaseTile* owner)
{
android::Mutex::Autolock lock(m_texturesLock);
// Sanity check that the tile does not already own a texture
if (owner->backTexture() && owner->backTexture()->owner() == owner) {
XLOG("same owner (%d, %d), getAvailableBackTexture(%x) => texture %x",
owner->x(), owner->y(), owner, owner->backTexture());
if (owner->isLayerTile())
m_availableTilesTextures.remove(m_availableTilesTextures.find(owner->backTexture()));
else
m_availableTextures.remove(m_availableTextures.find(owner->backTexture()));
return owner->backTexture();
}
WTF::Vector<BaseTileTexture*>* availableTexturePool;
if (owner->isLayerTile()) {
availableTexturePool = &m_availableTilesTextures;
} else {
availableTexturePool = &m_availableTextures;
}
// 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. If we find a tile in the same page with a different scale,
// it's old and not visible. Break with that one
// 5. Otherwise, use the least recently prepared tile, but ignoring tiles
// drawn in the last frame to avoid flickering
BaseTileTexture* farthestTexture = 0;
unsigned long long oldestDrawCount = getDrawGLCount() - 1;
const unsigned int max = availableTexturePool->size();
for (unsigned int i = 0; i < max; i++) {
BaseTileTexture* texture = (*availableTexturePool)[i];
BaseTile* currentOwner = static_cast<BaseTile*>(texture->owner());
if (texture->busy()) {
// don't bother, since the acquire() will likely fail
continue;
}
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;
}
if (currentOwner->painter() == owner->painter() && texture->scale() != owner->scale()) {
// if we render the back page with one scale, then another while
// still zooming, we recycle the tiles with the old scale instead of
// taking ones from the front page
farthestTexture = texture;
break;
}
unsigned long long textureDrawCount = currentOwner->drawCount();
if (oldestDrawCount > textureDrawCount) {
farthestTexture = texture;
oldestDrawCount = textureDrawCount;
}
}
if (farthestTexture) {
BaseTile* previousOwner = static_cast<BaseTile*>(farthestTexture->owner());
if (farthestTexture->acquire(owner)) {
if (previousOwner) {
previousOwner->removeTexture(farthestTexture);
XLOG("%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;
}
}
XLOG("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;
}
int TilesManager::maxTextureCount()
{
android::Mutex::Autolock lock(m_texturesLock);
return m_maxTextureCount;
}
int TilesManager::maxLayerTextureCount()
{
android::Mutex::Autolock lock(m_texturesLock);
return m_maxLayerTextureCount;
}
void TilesManager::setMaxTextureCount(int max)
{
XLOG("setMaxTextureCount: %d (current: %d, total:%d)",
max, m_maxTextureCount, MAX_TEXTURE_ALLOCATION);
if (m_maxTextureCount == MAX_TEXTURE_ALLOCATION ||
max <= m_maxTextureCount)
return;
android::Mutex::Autolock lock(m_texturesLock);
if (max < MAX_TEXTURE_ALLOCATION)
m_maxTextureCount = max;
else
m_maxTextureCount = MAX_TEXTURE_ALLOCATION;
allocateTiles();
}
void TilesManager::setMaxLayerTextureCount(int max)
{
XLOG("setMaxLayerTextureCount: %d (current: %d, total:%d)",
max, m_maxLayerTextureCount, MAX_TEXTURE_ALLOCATION);
if (!max && m_hasLayerTextures) {
double secondsSinceLayersUsed = WTF::currentTime() - m_lastTimeLayersUsed;
if (secondsSinceLayersUsed > LAYER_TEXTURES_DESTROY_TIMEOUT) {
unsigned long long sparedDrawCount = ~0; // by default, spare no textures
deallocateTexturesVector(sparedDrawCount, m_tilesTextures);
m_hasLayerTextures = false;
}
return;
}
m_lastTimeLayersUsed = WTF::currentTime();
if (m_maxLayerTextureCount == MAX_TEXTURE_ALLOCATION ||
max <= m_maxLayerTextureCount)
return;
android::Mutex::Autolock lock(m_texturesLock);
if (max < MAX_TEXTURE_ALLOCATION)
m_maxLayerTextureCount = max;
else
m_maxLayerTextureCount = MAX_TEXTURE_ALLOCATION;
allocateTiles();
m_hasLayerTextures = true;
}
float TilesManager::tileWidth()
{
return TILE_WIDTH;
}
float TilesManager::tileHeight()
{
return TILE_HEIGHT;
}
float TilesManager::layerTileWidth()
{
return LAYER_TILE_WIDTH;
}
float TilesManager::layerTileHeight()
{
return LAYER_TILE_HEIGHT;
}
void TilesManager::paintedSurfacesCleanup(GLWebViewState* state)
{
// PaintedSurfaces are created by LayerAndroid with a refcount of 1,
// and just transferred to new (corresponding) layers when a new layer tree
// is received.
// PaintedSurface also keep a reference on the Layer it currently has, so
// when we unref the tree of layer, those layers with a PaintedSurface will
// still be around if we do nothing.
// Here, if the surface does not have any associated layer, it means that we
// received a new layer tree without a corresponding layer (i.e. a layer
// using a texture has been removed by webkit).
// In that case, we remove the PaintedSurface from our list, and unref it.
// If the surface does have a layer, but the GLWebViewState associated to
// that layer is different from the one passed in parameter, it means we can
// also remove the surface (and we also remove/unref any layer that surface
// has). We do this when we deallocate GLWebViewState (i.e. the webview has
// been destroyed) and also when we switch to a page without
// composited layers.
WTF::Vector<PaintedSurface*> collect;
for (unsigned int i = 0; i < m_paintedSurfaces.size(); i++) {
PaintedSurface* surface = m_paintedSurfaces[i];
Layer* drawing = surface->drawingLayer();
Layer* painting = surface->paintingLayer();
XLOG("considering PS %p, drawing %p, painting %p", surface, drawing, painting);
bool drawingMatchesState = state && drawing && (drawing->state() == state);
bool paintingMatchesState = state && painting && (painting->state() == state);
if ((!painting && !drawing) || drawingMatchesState || paintingMatchesState) {
XLOG("trying to remove PS %p, painting %p, drawing %p, DMS %d, PMS %d",
surface, painting, drawing, drawingMatchesState, paintingMatchesState);
collect.append(surface);
}
}
for (unsigned int i = 0; i < collect.size(); i++) {
PaintedSurface* surface = collect[i];
m_paintedSurfaces.remove(m_paintedSurfaces.find(surface));
SkSafeUnref(surface);
}
}
void TilesManager::unregisterGLWebViewState(GLWebViewState* state)
{
// Discard the whole queue b/c we lost GL context already.
// Note the real updateTexImage will still wait for the next draw.
transferQueue()->discardQueue();
}
TilesManager* TilesManager::instance()
{
if (!gInstance) {
gInstance = new TilesManager();
XLOG("instance(), new gInstance is %x", gInstance);
XLOG("Waiting for the generator...");
gInstance->waitForGenerator();
XLOG("Generator ready!");
}
return gInstance;
}
TilesManager* TilesManager::gInstance = 0;
} // namespace WebCore
#endif // USE(ACCELERATED_COMPOSITING)