blob: ec88bb77e593694cb0f01ca8a2826a4b6e8f446f [file] [log] [blame]
// Copyright 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "config.h"
#include "cc/resource_provider.h"
#include <limits.h>
#include "base/debug/alias.h"
#include "base/hash_tables.h"
#include "base/stl_util.h"
#include "base/string_split.h"
#include "base/string_util.h"
#include "cc/gl_renderer.h" // For the GLC() macro.
#include "cc/texture_uploader.h"
#include "cc/transferable_resource.h"
#include "third_party/khronos/GLES2/gl2.h"
#include "third_party/khronos/GLES2/gl2ext.h"
#include "ui/gfx/rect.h"
#include "ui/gfx/vector2d.h"
#include <public/WebGraphicsContext3D.h>
using WebKit::WebGraphicsContext3D;
namespace {
// Temporary variables for debugging crashes in issue 151428 in canary.
// Do not use these!
const int g_debugMaxResourcesTracked = 64;
unsigned int g_debugZone = 0;
int64 g_debugResDestroyedCount = 0;
cc::ResourceProvider::ResourceId g_debugResDestroyed[g_debugMaxResourcesTracked] = { 0 };
}
namespace cc {
static GLenum textureToStorageFormat(GLenum textureFormat)
{
GLenum storageFormat = GL_RGBA8_OES;
switch (textureFormat) {
case GL_RGBA:
break;
case GL_BGRA_EXT:
storageFormat = GL_BGRA8_EXT;
break;
default:
NOTREACHED();
break;
}
return storageFormat;
}
static bool isTextureFormatSupportedForStorage(GLenum format)
{
return (format == GL_RGBA || format == GL_BGRA_EXT);
}
ResourceProvider::Resource::Resource()
: glId(0)
, pixels(0)
, pool(0)
, lockForReadCount(0)
, lockedForWrite(false)
, external(false)
, exported(false)
, markedForDeletion(false)
, size()
, format(0)
, type(static_cast<ResourceType>(0))
{
}
ResourceProvider::Resource::Resource(unsigned textureId, int pool, const gfx::Size& size, GLenum format)
: glId(textureId)
, pixels(0)
, pool(pool)
, lockForReadCount(0)
, lockedForWrite(false)
, external(false)
, exported(false)
, markedForDeletion(false)
, size(size)
, format(format)
, type(GLTexture)
{
}
ResourceProvider::Resource::Resource(uint8_t* pixels, int pool, const gfx::Size& size, GLenum format)
: glId(0)
, pixels(pixels)
, pool(pool)
, lockForReadCount(0)
, lockedForWrite(false)
, external(false)
, exported(false)
, markedForDeletion(false)
, size(size)
, format(format)
, type(Bitmap)
{
}
ResourceProvider::Child::Child()
{
}
ResourceProvider::Child::~Child()
{
}
scoped_ptr<ResourceProvider> ResourceProvider::create(GraphicsContext* context)
{
scoped_ptr<ResourceProvider> resourceProvider(new ResourceProvider(context));
if (!resourceProvider->initialize())
return scoped_ptr<ResourceProvider>();
return resourceProvider.Pass();
}
ResourceProvider::~ResourceProvider()
{
WebGraphicsContext3D* context3d = m_context->context3D();
if (!context3d || !context3d->makeContextCurrent())
return;
m_textureUploader.reset();
m_textureCopier.reset();
}
WebGraphicsContext3D* ResourceProvider::graphicsContext3D()
{
DCHECK(m_threadChecker.CalledOnValidThread());
return m_context->context3D();
}
bool ResourceProvider::inUseByConsumer(ResourceId id)
{
DCHECK(m_threadChecker.CalledOnValidThread());
ResourceMap::iterator it = m_resources.find(id);
CHECK(it != m_resources.end());
Resource* resource = &it->second;
return !!resource->lockForReadCount || resource->exported;
}
ResourceProvider::ResourceId ResourceProvider::createResource(int pool, const gfx::Size& size, GLenum format, TextureUsageHint hint)
{
switch (m_defaultResourceType) {
case GLTexture:
return createGLTexture(pool, size, format, hint);
case Bitmap:
DCHECK(format == GL_RGBA);
return createBitmap(pool, size);
}
CRASH();
return 0;
}
ResourceProvider::ResourceId ResourceProvider::createGLTexture(int pool, const gfx::Size& size, GLenum format, TextureUsageHint hint)
{
DCHECK(m_threadChecker.CalledOnValidThread());
unsigned textureId = 0;
WebGraphicsContext3D* context3d = m_context->context3D();
DCHECK(context3d);
GLC(context3d, textureId = context3d->createTexture());
GLC(context3d, context3d->bindTexture(GL_TEXTURE_2D, textureId));
GLC(context3d, context3d->texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
GLC(context3d, context3d->texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
GLC(context3d, context3d->texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE));
GLC(context3d, context3d->texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE));
if (m_useTextureUsageHint && hint == TextureUsageFramebuffer)
GLC(context3d, context3d->texParameteri(GL_TEXTURE_2D, GL_TEXTURE_USAGE_ANGLE, GL_FRAMEBUFFER_ATTACHMENT_ANGLE));
if (m_useTextureStorageExt && isTextureFormatSupportedForStorage(format)) {
GLenum storageFormat = textureToStorageFormat(format);
GLC(context3d, context3d->texStorage2DEXT(GL_TEXTURE_2D, 1, storageFormat, size.width(), size.height()));
} else
GLC(context3d, context3d->texImage2D(GL_TEXTURE_2D, 0, format, size.width(), size.height(), 0, format, GL_UNSIGNED_BYTE, 0));
ResourceId id = m_nextId++;
Resource resource(textureId, pool, size, format);
m_resources[id] = resource;
return id;
}
ResourceProvider::ResourceId ResourceProvider::createBitmap(int pool, const gfx::Size& size)
{
DCHECK(m_threadChecker.CalledOnValidThread());
uint8_t* pixels = new uint8_t[size.width() * size.height() * 4];
ResourceId id = m_nextId++;
Resource resource(pixels, pool, size, GL_RGBA);
m_resources[id] = resource;
return id;
}
ResourceProvider::ResourceId ResourceProvider::createResourceFromExternalTexture(unsigned textureId)
{
DCHECK(m_threadChecker.CalledOnValidThread());
DCHECK(m_context->context3D());
ResourceId id = m_nextId++;
Resource resource(textureId, 0, gfx::Size(), 0);
resource.external = true;
m_resources[id] = resource;
return id;
}
void ResourceProvider::deleteResource(ResourceId id)
{
DCHECK(m_threadChecker.CalledOnValidThread());
ResourceMap::iterator it = m_resources.find(id);
CHECK(it != m_resources.end());
Resource* resource = &it->second;
DCHECK(!resource->lockedForWrite);
DCHECK(!resource->lockForReadCount);
DCHECK(!resource->markedForDeletion);
if (resource->exported) {
resource->markedForDeletion = true;
return;
} else
deleteResourceInternal(it);
}
void ResourceProvider::deleteResourceInternal(ResourceMap::iterator it)
{
Resource* resource = &it->second;
if (resource->glId && !resource->external) {
WebGraphicsContext3D* context3d = m_context->context3D();
DCHECK(context3d);
GLC(context3d, context3d->deleteTexture(resource->glId));
}
if (resource->pixels)
delete[] resource->pixels;
g_debugResDestroyed[g_debugResDestroyedCount % g_debugMaxResourcesTracked] = (*it).first | g_debugZone;
m_resources.erase(it);
}
void ResourceProvider::deleteOwnedResources(int pool)
{
DCHECK(m_threadChecker.CalledOnValidThread());
ResourceIdArray toDelete;
for (ResourceMap::iterator it = m_resources.begin(); it != m_resources.end(); ++it) {
if (it->second.pool == pool && !it->second.external && !it->second.markedForDeletion)
toDelete.push_back(it->first);
}
for (ResourceIdArray::iterator it = toDelete.begin(); it != toDelete.end(); ++it)
deleteResource(*it);
}
ResourceProvider::ResourceType ResourceProvider::resourceType(ResourceId id)
{
ResourceMap::iterator it = m_resources.find(id);
CHECK(it != m_resources.end());
Resource* resource = &it->second;
return resource->type;
}
void ResourceProvider::setPixels(ResourceId id, const uint8_t* image, const gfx::Rect& imageRect, const gfx::Rect& sourceRect, const gfx::Vector2d& destOffset)
{
DCHECK(m_threadChecker.CalledOnValidThread());
ResourceMap::iterator it = m_resources.find(id);
CHECK(it != m_resources.end());
Resource* resource = &it->second;
DCHECK(!resource->lockedForWrite);
DCHECK(!resource->lockForReadCount);
DCHECK(!resource->external);
DCHECK(!resource->exported);
if (resource->glId) {
WebGraphicsContext3D* context3d = m_context->context3D();
DCHECK(context3d);
DCHECK(m_textureUploader.get());
context3d->bindTexture(GL_TEXTURE_2D, resource->glId);
m_textureUploader->upload(image,
imageRect,
sourceRect,
destOffset,
resource->format,
resource->size);
}
if (resource->pixels) {
SkBitmap srcFull;
srcFull.setConfig(SkBitmap::kARGB_8888_Config, imageRect.width(), imageRect.height());
srcFull.setPixels(const_cast<uint8_t*>(image));
SkBitmap srcSubset;
SkIRect skSourceRect = SkIRect::MakeXYWH(sourceRect.x(), sourceRect.y(), sourceRect.width(), sourceRect.height());
skSourceRect.offset(-imageRect.x(), -imageRect.y());
srcFull.extractSubset(&srcSubset, skSourceRect);
ScopedWriteLockSoftware lock(this, id);
SkCanvas* dest = lock.skCanvas();
dest->writePixels(srcSubset, destOffset.x(), destOffset.y());
}
}
size_t ResourceProvider::numBlockingUploads()
{
if (!m_textureUploader)
return 0;
return m_textureUploader->numBlockingUploads();
}
void ResourceProvider::markPendingUploadsAsNonBlocking()
{
if (!m_textureUploader)
return;
m_textureUploader->markPendingUploadsAsNonBlocking();
}
double ResourceProvider::estimatedUploadsPerSecond()
{
if (!m_textureUploader)
return 0.0;
return m_textureUploader->estimatedTexturesPerSecond();
}
void ResourceProvider::flushUploads()
{
if (!m_textureUploader)
return;
m_textureUploader->flush();
}
void ResourceProvider::flush()
{
DCHECK(m_threadChecker.CalledOnValidThread());
WebGraphicsContext3D* context3d = m_context->context3D();
if (context3d)
context3d->flush();
}
bool ResourceProvider::shallowFlushIfSupported()
{
DCHECK(m_threadChecker.CalledOnValidThread());
WebGraphicsContext3D* context3d = m_context->context3D();
if (!context3d || !m_useShallowFlush)
return false;
context3d->shallowFlushCHROMIUM();
return true;
}
const ResourceProvider::Resource* ResourceProvider::lockForRead(ResourceId id)
{
DCHECK(m_threadChecker.CalledOnValidThread());
ResourceMap::iterator it = m_resources.find(id);
if (it == m_resources.end()) {
int resourceCount = m_resources.size();
int64 resDestroyedCount = g_debugResDestroyedCount;
ResourceId resDestroyed[g_debugMaxResourcesTracked];
for (int64 i = 0; i < g_debugMaxResourcesTracked; ++i)
resDestroyed[i] = g_debugResDestroyed[i];
ResourceId resToDestroy = id;
base::debug::Alias(&resourceCount);
base::debug::Alias(&resDestroyedCount);
for (int64 i = 0; i < g_debugMaxResourcesTracked; ++i)
base::debug::Alias(&resDestroyed[i]);
base::debug::Alias(&resToDestroy);
CHECK(it != m_resources.end());
}
Resource* resource = &it->second;
DCHECK(!resource->lockedForWrite);
DCHECK(!resource->exported);
resource->lockForReadCount++;
return resource;
}
void ResourceProvider::unlockForRead(ResourceId id)
{
DCHECK(m_threadChecker.CalledOnValidThread());
ResourceMap::iterator it = m_resources.find(id);
CHECK(it != m_resources.end());
Resource* resource = &it->second;
DCHECK(resource->lockForReadCount > 0);
DCHECK(!resource->exported);
resource->lockForReadCount--;
}
const ResourceProvider::Resource* ResourceProvider::lockForWrite(ResourceId id)
{
DCHECK(m_threadChecker.CalledOnValidThread());
ResourceMap::iterator it = m_resources.find(id);
CHECK(it != m_resources.end());
Resource* resource = &it->second;
DCHECK(!resource->lockedForWrite);
DCHECK(!resource->lockForReadCount);
DCHECK(!resource->exported);
DCHECK(!resource->external);
resource->lockedForWrite = true;
return resource;
}
void ResourceProvider::unlockForWrite(ResourceId id)
{
DCHECK(m_threadChecker.CalledOnValidThread());
ResourceMap::iterator it = m_resources.find(id);
CHECK(it != m_resources.end());
Resource* resource = &it->second;
DCHECK(resource->lockedForWrite);
DCHECK(!resource->exported);
DCHECK(!resource->external);
resource->lockedForWrite = false;
}
ResourceProvider::ScopedReadLockGL::ScopedReadLockGL(ResourceProvider* resourceProvider, ResourceProvider::ResourceId resourceId)
: m_resourceProvider(resourceProvider)
, m_resourceId(resourceId)
, m_textureId(resourceProvider->lockForRead(resourceId)->glId)
{
DCHECK(m_textureId);
}
ResourceProvider::ScopedReadLockGL::~ScopedReadLockGL()
{
m_resourceProvider->unlockForRead(m_resourceId);
}
ResourceProvider::ScopedWriteLockGL::ScopedWriteLockGL(ResourceProvider* resourceProvider, ResourceProvider::ResourceId resourceId)
: m_resourceProvider(resourceProvider)
, m_resourceId(resourceId)
, m_textureId(resourceProvider->lockForWrite(resourceId)->glId)
{
DCHECK(m_textureId);
}
ResourceProvider::ScopedWriteLockGL::~ScopedWriteLockGL()
{
m_resourceProvider->unlockForWrite(m_resourceId);
}
void ResourceProvider::populateSkBitmapWithResource(SkBitmap* skBitmap, const Resource* resource)
{
DCHECK(resource->pixels);
DCHECK(resource->format == GL_RGBA);
skBitmap->setConfig(SkBitmap::kARGB_8888_Config, resource->size.width(), resource->size.height());
skBitmap->setPixels(resource->pixels);
}
ResourceProvider::ScopedReadLockSoftware::ScopedReadLockSoftware(ResourceProvider* resourceProvider, ResourceProvider::ResourceId resourceId)
: m_resourceProvider(resourceProvider)
, m_resourceId(resourceId)
{
ResourceProvider::populateSkBitmapWithResource(&m_skBitmap, resourceProvider->lockForRead(resourceId));
}
ResourceProvider::ScopedReadLockSoftware::~ScopedReadLockSoftware()
{
m_resourceProvider->unlockForRead(m_resourceId);
}
ResourceProvider::ScopedWriteLockSoftware::ScopedWriteLockSoftware(ResourceProvider* resourceProvider, ResourceProvider::ResourceId resourceId)
: m_resourceProvider(resourceProvider)
, m_resourceId(resourceId)
{
ResourceProvider::populateSkBitmapWithResource(&m_skBitmap, resourceProvider->lockForWrite(resourceId));
m_skCanvas.reset(new SkCanvas(m_skBitmap));
}
ResourceProvider::ScopedWriteLockSoftware::~ScopedWriteLockSoftware()
{
m_resourceProvider->unlockForWrite(m_resourceId);
}
ResourceProvider::ResourceProvider(GraphicsContext* context)
: m_context(context)
, m_nextId(1)
, m_nextChild(1)
, m_defaultResourceType(GLTexture)
, m_useTextureStorageExt(false)
, m_useTextureUsageHint(false)
, m_useShallowFlush(false)
, m_maxTextureSize(0)
{
}
bool ResourceProvider::initialize()
{
DCHECK(m_threadChecker.CalledOnValidThread());
WebGraphicsContext3D* context3d = m_context->context3D();
if (!context3d) {
m_maxTextureSize = INT_MAX / 2;
return true;
}
if (!context3d->makeContextCurrent())
return false;
std::string extensionsString = UTF16ToASCII(context3d->getString(GL_EXTENSIONS));
std::vector<std::string> extensions;
base::SplitString(extensionsString, ' ', &extensions);
bool useMapSub = false;
bool useBindUniform = false;
for (size_t i = 0; i < extensions.size(); ++i) {
if (extensions[i] == "GL_EXT_texture_storage")
m_useTextureStorageExt = true;
else if (extensions[i] == "GL_ANGLE_texture_usage")
m_useTextureUsageHint = true;
else if (extensions[i] == "GL_CHROMIUM_map_sub")
useMapSub = true;
else if (extensions[i] == "GL_CHROMIUM_shallow_flush")
m_useShallowFlush = true;
else if (extensions[i] == "GL_CHROMIUM_bind_uniform_location")
useBindUniform = true;
}
m_textureCopier = AcceleratedTextureCopier::create(context3d, useBindUniform);
m_textureUploader = TextureUploader::create(context3d, useMapSub, m_useShallowFlush);
GLC(context3d, context3d->getIntegerv(GL_MAX_TEXTURE_SIZE, &m_maxTextureSize));
return true;
}
int ResourceProvider::createChild(int pool)
{
DCHECK(m_threadChecker.CalledOnValidThread());
Child childInfo;
childInfo.pool = pool;
int child = m_nextChild++;
m_children[child] = childInfo;
return child;
}
void ResourceProvider::destroyChild(int child)
{
DCHECK(m_threadChecker.CalledOnValidThread());
ChildMap::iterator it = m_children.find(child);
DCHECK(it != m_children.end());
deleteOwnedResources(it->second.pool);
m_children.erase(it);
trimMailboxDeque();
}
const ResourceProvider::ResourceIdMap& ResourceProvider::getChildToParentMap(int child) const
{
DCHECK(m_threadChecker.CalledOnValidThread());
ChildMap::const_iterator it = m_children.find(child);
DCHECK(it != m_children.end());
return it->second.childToParentMap;
}
void ResourceProvider::prepareSendToParent(const ResourceIdArray& resources, TransferableResourceList* list)
{
DCHECK(m_threadChecker.CalledOnValidThread());
list->sync_point = 0;
list->resources.clear();
WebGraphicsContext3D* context3d = m_context->context3D();
if (!context3d || !context3d->makeContextCurrent()) {
// FIXME: Implement this path for software compositing.
return;
}
for (ResourceIdArray::const_iterator it = resources.begin(); it != resources.end(); ++it) {
TransferableResource resource;
if (transferResource(context3d, *it, &resource)) {
m_resources.find(*it)->second.exported = true;
list->resources.push_back(resource);
}
}
if (list->resources.size())
list->sync_point = context3d->insertSyncPoint();
}
void ResourceProvider::prepareSendToChild(int child, const ResourceIdArray& resources, TransferableResourceList* list)
{
DCHECK(m_threadChecker.CalledOnValidThread());
list->sync_point = 0;
list->resources.clear();
WebGraphicsContext3D* context3d = m_context->context3D();
if (!context3d || !context3d->makeContextCurrent()) {
// FIXME: Implement this path for software compositing.
return;
}
Child& childInfo = m_children.find(child)->second;
for (ResourceIdArray::const_iterator it = resources.begin(); it != resources.end(); ++it) {
TransferableResource resource;
if (!transferResource(context3d, *it, &resource))
NOTREACHED();
DCHECK(childInfo.parentToChildMap.find(*it) != childInfo.parentToChildMap.end());
resource.id = childInfo.parentToChildMap[*it];
childInfo.parentToChildMap.erase(*it);
childInfo.childToParentMap.erase(resource.id);
list->resources.push_back(resource);
deleteResource(*it);
}
if (list->resources.size())
list->sync_point = context3d->insertSyncPoint();
}
void ResourceProvider::receiveFromChild(int child, const TransferableResourceList& resources)
{
DCHECK(m_threadChecker.CalledOnValidThread());
WebGraphicsContext3D* context3d = m_context->context3D();
if (!context3d || !context3d->makeContextCurrent()) {
// FIXME: Implement this path for software compositing.
return;
}
if (resources.sync_point) {
// NOTE: If the parent is a browser and the child a renderer, the parent
// is not supposed to have its context wait, because that could induce
// deadlocks and/or security issues. The caller is responsible for
// waiting asynchronously, and resetting sync_point before calling this.
// However if the parent is a renderer (e.g. browser tag), it may be ok
// (and is simpler) to wait.
GLC(context3d, context3d->waitSyncPoint(resources.sync_point));
}
Child& childInfo = m_children.find(child)->second;
for (TransferableResourceArray::const_iterator it = resources.resources.begin(); it != resources.resources.end(); ++it) {
unsigned textureId;
GLC(context3d, textureId = context3d->createTexture());
GLC(context3d, context3d->bindTexture(GL_TEXTURE_2D, textureId));
GLC(context3d, context3d->consumeTextureCHROMIUM(GL_TEXTURE_2D, it->mailbox.name));
ResourceId id = m_nextId++;
Resource resource(textureId, childInfo.pool, it->size, it->format);
m_resources[id] = resource;
m_mailboxes.push_back(it->mailbox);
childInfo.parentToChildMap[id] = it->id;
childInfo.childToParentMap[it->id] = id;
}
}
void ResourceProvider::receiveFromParent(const TransferableResourceList& resources)
{
DCHECK(m_threadChecker.CalledOnValidThread());
WebGraphicsContext3D* context3d = m_context->context3D();
if (!context3d || !context3d->makeContextCurrent()) {
// FIXME: Implement this path for software compositing.
return;
}
if (resources.sync_point)
GLC(context3d, context3d->waitSyncPoint(resources.sync_point));
for (TransferableResourceArray::const_iterator it = resources.resources.begin(); it != resources.resources.end(); ++it) {
ResourceMap::iterator mapIterator = m_resources.find(it->id);
DCHECK(mapIterator != m_resources.end());
Resource* resource = &mapIterator->second;
DCHECK(resource->exported);
resource->exported = false;
GLC(context3d, context3d->bindTexture(GL_TEXTURE_2D, resource->glId));
GLC(context3d, context3d->consumeTextureCHROMIUM(GL_TEXTURE_2D, it->mailbox.name));
m_mailboxes.push_back(it->mailbox);
if (resource->markedForDeletion)
deleteResourceInternal(mapIterator);
}
}
bool ResourceProvider::transferResource(WebGraphicsContext3D* context, ResourceId id, TransferableResource* resource)
{
DCHECK(m_threadChecker.CalledOnValidThread());
ResourceMap::const_iterator it = m_resources.find(id);
CHECK(it != m_resources.end());
const Resource* source = &it->second;
DCHECK(!source->lockedForWrite);
DCHECK(!source->lockForReadCount);
DCHECK(!source->external);
if (source->exported)
return false;
resource->id = id;
resource->format = source->format;
resource->size = source->size;
if (!m_mailboxes.empty()) {
resource->mailbox = m_mailboxes.front();
m_mailboxes.pop_front();
}
else
GLC(context, context->genMailboxCHROMIUM(resource->mailbox.name));
GLC(context, context->bindTexture(GL_TEXTURE_2D, source->glId));
GLC(context, context->produceTextureCHROMIUM(GL_TEXTURE_2D, resource->mailbox.name));
return true;
}
void ResourceProvider::trimMailboxDeque()
{
// Trim the mailbox deque to the maximum number of resources we may need to
// send.
// If we have a parent, any non-external resource not already transfered is
// eligible to be sent to the parent. Otherwise, all resources belonging to
// a child might need to be sent back to the child.
size_t maxMailboxCount = 0;
if (m_context->capabilities().hasParentCompositor) {
for (ResourceMap::iterator it = m_resources.begin(); it != m_resources.end(); ++it) {
if (!it->second.exported && !it->second.external)
++maxMailboxCount;
}
} else {
base::hash_set<int> childPoolSet;
for (ChildMap::iterator it = m_children.begin(); it != m_children.end(); ++it)
childPoolSet.insert(it->second.pool);
for (ResourceMap::iterator it = m_resources.begin(); it != m_resources.end(); ++it) {
if (ContainsKey(childPoolSet, it->second.pool))
++maxMailboxCount;
}
}
while (m_mailboxes.size() > maxMailboxCount)
m_mailboxes.pop_front();
}
void ResourceProvider::debugNotifyEnterZone(unsigned int zone)
{
g_debugZone = zone;
}
void ResourceProvider::debugNotifyLeaveZone()
{
g_debugZone = 0;
}
} // namespace cc