blob: d65c4daddbbb2598eb0fa24171c4e6af14dd74c8 [file] [log] [blame]
/*
* Copyright (C) 2011, 2012 Research In Motion Limited. All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "config.h"
#include "TextureCacheCompositingThread.h"
#if USE(ACCELERATED_COMPOSITING)
#include "IntRect.h"
#include <GLES2/gl2.h>
#include <SkBitmap.h>
#define DEBUG_TEXTURE_MEMORY_USAGE 0
namespace WebCore {
static const int defaultMemoryLimit = 64 * 1024 * 1024; // Measured in bytes.
// Used to protect a newly created texture from being immediately evicted
// before someone has a chance to protect it for legitimate reasons.
class TextureProtector {
public:
TextureProtector(Texture* texture)
: m_texture(texture)
{
m_texture->protect();
}
~TextureProtector()
{
m_texture->unprotect();
}
private:
Texture* m_texture;
};
TextureCacheCompositingThread::TextureCacheCompositingThread()
: m_memoryUsage(0)
, m_memoryLimit(defaultMemoryLimit)
{
}
unsigned TextureCacheCompositingThread::allocateTextureId()
{
unsigned texid;
glGenTextures(1, &texid);
glBindTexture(GL_TEXTURE_2D, texid);
if (!glIsTexture(texid))
return 0;
// Do basic linear filtering on resize.
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
// NPOT textures in GL ES only work when the wrap mode is set to GL_CLAMP_TO_EDGE.
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
return texid;
}
void TextureCacheCompositingThread::freeTextureId(unsigned id)
{
if (id)
glDeleteTextures(1, &id);
}
void TextureCacheCompositingThread::collectGarbage()
{
int delta = 0;
for (Garbage::iterator it = m_garbage.begin(); it != m_garbage.end(); ++it) {
ZombieTexture& zombie = *it;
freeTextureId(zombie.id);
delta += zombie.size.width() * zombie.size.height() * Texture::bytesPerPixel();
}
m_garbage.clear();
if (delta)
decMemoryUsage(delta);
}
void TextureCacheCompositingThread::textureResized(Texture* texture, const IntSize& oldSize)
{
int delta = (texture->width() * texture->height() - oldSize.width() * oldSize.height()) * Texture::bytesPerPixel();
incMemoryUsage(delta);
if (delta > 0)
prune();
}
void TextureCacheCompositingThread::textureDestroyed(Texture* texture)
{
if (texture->isColor()) {
m_garbage.append(ZombieTexture(texture));
return;
}
evict(m_textures.find(texture));
}
bool TextureCacheCompositingThread::install(Texture* texture)
{
if (!texture)
return true;
if (!texture->hasTexture()) {
unsigned textureId = allocateTextureId();
if (!textureId)
return false;
texture->setTextureId(textureId);
}
if (!texture->isColor()) {
if (!m_textures.contains(texture))
m_textures.add(texture);
}
return true;
}
void TextureCacheCompositingThread::evict(const TextureSet::iterator& it)
{
if (it == m_textures.end())
return;
Texture* texture = *it;
if (texture->hasTexture())
m_garbage.append(ZombieTexture(texture));
texture->setTextureId(0);
m_textures.remove(it);
}
void TextureCacheCompositingThread::textureAccessed(Texture* texture)
{
if (texture->isColor())
return;
TextureSet::iterator it = m_textures.find(texture);
if (it == m_textures.end())
return;
m_textures.remove(it);
m_textures.add(texture);
}
TextureCacheCompositingThread* textureCacheCompositingThread()
{
static TextureCacheCompositingThread* staticCache = new TextureCacheCompositingThread;
return staticCache;
}
void TextureCacheCompositingThread::prune(size_t limit)
{
while (m_memoryUsage > limit) {
bool found = false;
for (TextureSet::iterator it = m_textures.begin(); it != m_textures.end(); ++it) {
Texture* texture = *it;
if (texture->isProtected())
continue;
evict(it);
found = true;
break;
}
if (!found)
break;
}
}
void TextureCacheCompositingThread::clear()
{
m_cache.clear();
m_colors.clear();
collectGarbage();
}
void TextureCacheCompositingThread::setMemoryUsage(size_t memoryUsage)
{
m_memoryUsage = memoryUsage;
#if DEBUG_TEXTURE_MEMORY_USAGE
fprintf(stderr, "Texture memory usage %u kB\n", m_memoryUsage / 1024);
#endif
}
PassRefPtr<Texture> TextureCacheCompositingThread::textureForTiledContents(const SkBitmap& contents, const IntRect& tileRect, const TileIndex& index, bool isOpaque)
{
HashMap<ContentsKey, TextureMap>::iterator it = m_cache.add(key(contents), TextureMap()).iterator;
TextureMap& map = (*it).value;
TextureMap::iterator jt = map.add(index, RefPtr<Texture>()).iterator;
RefPtr<Texture> texture = (*jt).value;
if (!texture) {
texture = createTexture();
#if DEBUG_TEXTURE_MEMORY_USAGE
fprintf(stderr, "Creating texture 0x%x for 0x%x+%d @ (%d, %d)\n", texture.get(), contents.pixelRef(), contents.pixelRefOffset(), index.i(), index.j());
#endif
map.set(index, texture);
}
// Protect newly created texture from being evicted.
TextureProtector protector(texture.get());
IntSize contentsSize(contents.width(), contents.height());
IntRect dirtyRect(IntPoint(), contentsSize);
if (tileRect.size() != texture->size()) {
#if DEBUG_TEXTURE_MEMORY_USAGE
fprintf(stderr, "Updating texture 0x%x for 0x%x+%d @ (%d, %d)\n", texture.get(), contents.pixelRef(), contents.pixelRefOffset(), index.i(), index.j());
#endif
texture->updateContents(contents, dirtyRect, tileRect, isOpaque);
}
return texture.release();
}
PassRefPtr<Texture> TextureCacheCompositingThread::textureForColor(const Color& color)
{
// Just to make sure we don't get fooled by some malicious web page
// into caching millions of color textures.
if (m_colors.size() > 100)
m_colors.clear();
ColorTextureMap::iterator it = m_colors.find(color);
RefPtr<Texture> texture;
if (it == m_colors.end()) {
texture = Texture::create(true /* isColor */);
#if DEBUG_TEXTURE_MEMORY_USAGE
fprintf(stderr, "Creating texture 0x%x for color 0x%x\n", texture.get(), color.rgb());
#endif
m_colors.set(color, texture);
} else
texture = (*it).value;
// Color textures can't be evicted, so no need for TextureProtector.
if (texture->size() != IntSize(1, 1))
texture->setContentsToColor(color);
return texture.release();
}
PassRefPtr<Texture> TextureCacheCompositingThread::updateContents(const RefPtr<Texture>& textureIn, const SkBitmap& contents, const IntRect& dirtyRect, const IntRect& tileRect, bool isOpaque)
{
RefPtr<Texture> texture(textureIn);
// If the texture was 0, or needs to transition from a solid color texture to a contents texture,
// create a new texture.
if (!texture || texture->isColor())
texture = createTexture();
// Protect newly created texture from being evicted.
TextureProtector protector(texture.get());
texture->updateContents(contents, dirtyRect, tileRect, isOpaque);
return texture.release();
}
TextureCacheCompositingThread::ContentsKey TextureCacheCompositingThread::key(const SkBitmap& contents)
{
// The pixelRef is an address, and addresses can be reused by the allocator
// so it's unsuitable to use as a key. Instead, grab the generation, which
// is globally unique according to the current implementation in
// SkPixelRef.cpp.
uint32_t generation = contents.getGenerationID();
// If the generation is equal to the deleted value, use something else that
// is unlikely to correspond to a generation currently in use or soon to be
// in use.
uint32_t deletedValue = 0;
HashTraits<uint32_t>::constructDeletedValue(deletedValue);
if (generation == deletedValue) {
// This strategy works as long as the deleted value is -1.
ASSERT(deletedValue == static_cast<uint32_t>(-1));
generation = deletedValue / 2;
}
// Now the generation alone does not uniquely identify the texture contents.
// The same pixelref can be reused in another bitmap but with a different
// offset (somewhat contrived, but possible).
return std::make_pair(generation, contents.pixelRefOffset());
}
} // namespace WebCore
#endif