// 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 "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/platform_color.h"
#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 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)
    , glPixelBufferId(0)
    , glUploadQueryId(0)
    , pixels(0)
    , pixelBuffer(0)
    , lockForReadCount(0)
    , lockedForWrite(false)
    , external(false)
    , exported(false)
    , markedForDeletion(false)
    , pendingSetPixels(false)
    , size()
    , format(0)
    , filter(0)
    , type(static_cast<ResourceType>(0))
{
}

ResourceProvider::Resource::Resource(unsigned textureId, const gfx::Size& size, GLenum format, GLenum filter)
    : glId(textureId)
    , glPixelBufferId(0)
    , glUploadQueryId(0)
    , pixels(0)
    , pixelBuffer(0)
    , lockForReadCount(0)
    , lockedForWrite(false)
    , external(false)
    , exported(false)
    , markedForDeletion(false)
    , pendingSetPixels(false)
    , size(size)
    , format(format)
    , filter(filter)
    , type(GLTexture)
{
}

ResourceProvider::Resource::Resource(uint8_t* pixels, const gfx::Size& size, GLenum format, GLenum filter)
    : glId(0)
    , glPixelBufferId(0)
    , glUploadQueryId(0)
    , pixels(pixels)
    , pixelBuffer(0)
    , lockForReadCount(0)
    , lockedForWrite(false)
    , external(false)
    , exported(false)
    , markedForDeletion(false)
    , pendingSetPixels(false)
    , size(size)
    , format(format)
    , filter(filter)
    , type(Bitmap)
{
}

ResourceProvider::Child::Child()
{
}

ResourceProvider::Child::~Child()
{
}

scoped_ptr<ResourceProvider> ResourceProvider::create(OutputSurface* context)
{
    scoped_ptr<ResourceProvider> resourceProvider(new ResourceProvider(context));
    if (!resourceProvider->initialize())
        return scoped_ptr<ResourceProvider>();
    return resourceProvider.Pass();
}

ResourceProvider::~ResourceProvider()
{
    WebGraphicsContext3D* context3d = m_outputSurface->Context3D();
    if (!context3d || !context3d->makeContextCurrent())
        return;
    m_textureUploader.reset();
    m_textureCopier.reset();
}

WebGraphicsContext3D* ResourceProvider::graphicsContext3D()
{
    DCHECK(m_threadChecker.CalledOnValidThread());
    return m_outputSurface->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(const gfx::Size& size, GLenum format, TextureUsageHint hint)
{
    switch (m_defaultResourceType) {
    case GLTexture:
        return createGLTexture(size, format, GL_TEXTURE_POOL_UNMANAGED_CHROMIUM, hint);
    case Bitmap:
        DCHECK(format == GL_RGBA);
        return createBitmap(size);
    }

    LOG(FATAL) << "Invalid default resource type.";
    return 0;
}

ResourceProvider::ResourceId ResourceProvider::createManagedResource(const gfx::Size& size, GLenum format, TextureUsageHint hint)
{
    switch (m_defaultResourceType) {
    case GLTexture:
        return createGLTexture(size, format, GL_TEXTURE_POOL_MANAGED_CHROMIUM, hint);
    case Bitmap:
        DCHECK(format == GL_RGBA);
        return createBitmap(size);
    }

    LOG(FATAL) << "Invalid default resource type.";
    return 0;
}

ResourceProvider::ResourceId ResourceProvider::createGLTexture(const gfx::Size& size, GLenum format, GLenum texturePool, TextureUsageHint hint)
{
    DCHECK_LE(size.width(), m_maxTextureSize);
    DCHECK_LE(size.height(), m_maxTextureSize);

    DCHECK(m_threadChecker.CalledOnValidThread());
    unsigned textureId = 0;
    WebGraphicsContext3D* context3d = m_outputSurface->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));
    GLC(context3d, context3d->texParameteri(GL_TEXTURE_2D, GL_TEXTURE_POOL_CHROMIUM, texturePool));

    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, size, format, GL_LINEAR);
    m_resources[id] = resource;
    return id;
}

ResourceProvider::ResourceId ResourceProvider::createBitmap(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, size, GL_RGBA, GL_LINEAR);
    m_resources[id] = resource;
    return id;
}

ResourceProvider::ResourceId ResourceProvider::createResourceFromExternalTexture(unsigned textureId)
{
    DCHECK(m_threadChecker.CalledOnValidThread());

    WebGraphicsContext3D* context3d = m_outputSurface->Context3D();
    DCHECK(context3d);
    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));

    ResourceId id = m_nextId++;
    Resource resource(textureId, gfx::Size(), 0, GL_LINEAR);
    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_outputSurface->Context3D();
        DCHECK(context3d);
        GLC(context3d, context3d->deleteTexture(resource->glId));
    }
    if (resource->glUploadQueryId) {
        WebGraphicsContext3D* context3d = m_outputSurface->Context3D();
        DCHECK(context3d);
        GLC(context3d, context3d->deleteQueryEXT(resource->glUploadQueryId));
    }
    if (resource->glPixelBufferId) {
        WebGraphicsContext3D* context3d = m_outputSurface->Context3D();
        DCHECK(context3d);
        GLC(context3d, context3d->deleteBuffer(resource->glPixelBufferId));
    }
    if (resource->pixels)
        delete[] resource->pixels;
    if (resource->pixelBuffer)
        delete[] resource->pixelBuffer;

    m_resources.erase(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_outputSurface->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) {
        DCHECK(resource->format == GL_RGBA);
        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_outputSurface->Context3D();
    if (context3d)
        context3d->flush();
}

bool ResourceProvider::shallowFlushIfSupported()
{
    DCHECK(m_threadChecker.CalledOnValidThread());
    WebGraphicsContext3D* context3d = m_outputSurface->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);
    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::ScopedSamplerGL::ScopedSamplerGL(ResourceProvider* resourceProvider, ResourceProvider::ResourceId resourceId, GLenum target, GLenum filter)
    : ScopedReadLockGL(resourceProvider, resourceId)
{
    resourceProvider->bindForSampling(resourceId, target, filter);
}

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(OutputSurface* context)
    : m_outputSurface(context)
    , m_nextId(1)
    , m_nextChild(1)
    , m_defaultResourceType(GLTexture)
    , m_useTextureStorageExt(false)
    , m_useTextureUsageHint(false)
    , m_useShallowFlush(false)
    , m_maxTextureSize(0)
    , m_bestTextureFormat(0)
{
}

bool ResourceProvider::initialize()
{
    DCHECK(m_threadChecker.CalledOnValidThread());
    WebGraphicsContext3D* context3d = m_outputSurface->Context3D();
    if (!context3d) {
        m_maxTextureSize = INT_MAX / 2;
        m_bestTextureFormat = GL_RGBA;
        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;
    bool useBGRA = 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;
        else if (extensions[i] == "GL_EXT_texture_format_BGRA8888")
          useBGRA = 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));
    m_bestTextureFormat = PlatformColor::bestTextureFormat(context3d, useBGRA);
    return true;
}

int ResourceProvider::createChild()
{
    DCHECK(m_threadChecker.CalledOnValidThread());
    Child childInfo;
    int child = m_nextChild++;
    m_children[child] = childInfo;
    return child;
}

void ResourceProvider::destroyChild(int child_id)
{
    DCHECK(m_threadChecker.CalledOnValidThread());
    ChildMap::iterator it = m_children.find(child_id);
    DCHECK(it != m_children.end());
    Child& child = it->second;
    for (ResourceIdMap::iterator child_it = child.childToParentMap.begin(); child_it != child.childToParentMap.end(); ++child_it)
        deleteResource(child_it->second);
    m_children.erase(it);
}

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_outputSurface->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_outputSurface->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_outputSurface->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, it->size, it->format, it->filter);
        resource.mailbox.setName(it->mailbox.name);
        m_resources[id] = resource;
        childInfo.parentToChildMap[id] = it->id;
        childInfo.childToParentMap[it->id] = id;
    }
}

void ResourceProvider::receiveFromParent(const TransferableResourceList& resources)
{
    DCHECK(m_threadChecker.CalledOnValidThread());
    WebGraphicsContext3D* context3d = m_outputSurface->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;
        resource->mailbox.setName(it->mailbox.name);
        GLC(context3d, context3d->bindTexture(GL_TEXTURE_2D, resource->glId));
        GLC(context3d, context3d->consumeTextureCHROMIUM(GL_TEXTURE_2D, it->mailbox.name));
        if (resource->markedForDeletion)
            deleteResourceInternal(mapIterator);
    }
}

bool ResourceProvider::transferResource(WebGraphicsContext3D* context, ResourceId id, TransferableResource* resource)
{
    DCHECK(m_threadChecker.CalledOnValidThread());
    WebGraphicsContext3D* context3d = m_outputSurface->Context3D();
    ResourceMap::iterator it = m_resources.find(id);
    CHECK(it != m_resources.end());
    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->filter = source->filter;
    resource->size = source->size;

    if (source->mailbox.isZero()) {
      GLbyte name[GL_MAILBOX_SIZE_CHROMIUM];
      GLC(context3d, context3d->genMailboxCHROMIUM(name));
      source->mailbox.setName(name);
    }

    resource->mailbox = source->mailbox;
    GLC(context, context->bindTexture(GL_TEXTURE_2D, source->glId));
    GLC(context, context->produceTextureCHROMIUM(GL_TEXTURE_2D, resource->mailbox.name));
    return true;
}

void ResourceProvider::acquirePixelBuffer(ResourceId id)
{
    DCHECK(m_threadChecker.CalledOnValidThread());
    ResourceMap::iterator it = m_resources.find(id);
    CHECK(it != m_resources.end());
    Resource* resource = &it->second;
    DCHECK(!resource->external);
    DCHECK(!resource->exported);

    if (resource->glId) {
        WebGraphicsContext3D* context3d = m_outputSurface->Context3D();
        DCHECK(context3d);
        if (!resource->glPixelBufferId)
            resource->glPixelBufferId = context3d->createBuffer();
        context3d->bindBuffer(
            GL_PIXEL_UNPACK_TRANSFER_BUFFER_CHROMIUM,
            resource->glPixelBufferId);
        context3d->bufferData(
            GL_PIXEL_UNPACK_TRANSFER_BUFFER_CHROMIUM,
            resource->size.width() * resource->size.height() * 4,
            NULL,
            GL_DYNAMIC_DRAW);
        context3d->bindBuffer(GL_PIXEL_UNPACK_TRANSFER_BUFFER_CHROMIUM, 0);
    }

    if (resource->pixels) {
        if (resource->pixelBuffer)
            return;

        resource->pixelBuffer = new uint8_t[
            resource->size.width() * resource->size.height() * 4];
    }
}

void ResourceProvider::releasePixelBuffer(ResourceId id)
{
    DCHECK(m_threadChecker.CalledOnValidThread());
    ResourceMap::iterator it = m_resources.find(id);
    CHECK(it != m_resources.end());
    Resource* resource = &it->second;
    DCHECK(!resource->external);
    DCHECK(!resource->exported);

    if (resource->glId) {
        DCHECK(resource->glPixelBufferId);
        WebGraphicsContext3D* context3d = m_outputSurface->Context3D();
        DCHECK(context3d);
        context3d->bindBuffer(
            GL_PIXEL_UNPACK_TRANSFER_BUFFER_CHROMIUM,
            resource->glPixelBufferId);
        context3d->bufferData(
            GL_PIXEL_UNPACK_TRANSFER_BUFFER_CHROMIUM,
            0,
            NULL,
            GL_DYNAMIC_DRAW);
        context3d->bindBuffer(GL_PIXEL_UNPACK_TRANSFER_BUFFER_CHROMIUM, 0);
    }

    if (resource->pixels) {
        if (!resource->pixelBuffer)
            return;
        delete[] resource->pixelBuffer;
        resource->pixelBuffer = 0;
    }
}

uint8_t* ResourceProvider::mapPixelBuffer(ResourceId id)
{
    DCHECK(m_threadChecker.CalledOnValidThread());
    ResourceMap::iterator it = m_resources.find(id);
    CHECK(it != m_resources.end());
    Resource* resource = &it->second;
    DCHECK(!resource->external);
    DCHECK(!resource->exported);

    if (resource->glId) {
        WebGraphicsContext3D* context3d = m_outputSurface->Context3D();
        DCHECK(context3d);
        DCHECK(resource->glPixelBufferId);
        context3d->bindBuffer(
            GL_PIXEL_UNPACK_TRANSFER_BUFFER_CHROMIUM,
            resource->glPixelBufferId);
        uint8_t* image = static_cast<uint8_t*>(
            context3d->mapBufferCHROMIUM(
                GL_PIXEL_UNPACK_TRANSFER_BUFFER_CHROMIUM, GL_WRITE_ONLY));
        context3d->bindBuffer(GL_PIXEL_UNPACK_TRANSFER_BUFFER_CHROMIUM, 0);
        DCHECK(image);
        return image;
    }

    if (resource->pixels)
      return resource->pixelBuffer;

    return NULL;
}

void ResourceProvider::unmapPixelBuffer(ResourceId id)
{
    DCHECK(m_threadChecker.CalledOnValidThread());
    ResourceMap::iterator it = m_resources.find(id);
    CHECK(it != m_resources.end());
    Resource* resource = &it->second;
    DCHECK(!resource->external);
    DCHECK(!resource->exported);

    if (resource->glId) {
        WebGraphicsContext3D* context3d = m_outputSurface->Context3D();
        DCHECK(context3d);
        DCHECK(resource->glPixelBufferId);
        context3d->bindBuffer(
            GL_PIXEL_UNPACK_TRANSFER_BUFFER_CHROMIUM,
            resource->glPixelBufferId);
        context3d->unmapBufferCHROMIUM(
            GL_PIXEL_UNPACK_TRANSFER_BUFFER_CHROMIUM);
        context3d->bindBuffer(GL_PIXEL_UNPACK_TRANSFER_BUFFER_CHROMIUM, 0);
    }
}

void ResourceProvider::setPixelsFromBuffer(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->external);
    DCHECK(!resource->exported);

    if (resource->glId) {
        WebGraphicsContext3D* context3d = m_outputSurface->Context3D();
        DCHECK(context3d);
        DCHECK(resource->glPixelBufferId);
        context3d->bindTexture(GL_TEXTURE_2D, resource->glId);
        context3d->bindBuffer(
            GL_PIXEL_UNPACK_TRANSFER_BUFFER_CHROMIUM,
            resource->glPixelBufferId);
        context3d->texSubImage2D(GL_TEXTURE_2D,
                                 0, /* level */
                                 0, /* x */
                                 0, /* y */
                                 resource->size.width(),
                                 resource->size.height(),
                                 resource->format,
                                 GL_UNSIGNED_BYTE,
                                 NULL);
        context3d->bindBuffer(GL_PIXEL_UNPACK_TRANSFER_BUFFER_CHROMIUM, 0);
    }

    if (resource->pixels) {
        DCHECK(resource->pixelBuffer);
        DCHECK(resource->format == GL_RGBA);
        SkBitmap src;
        src.setConfig(SkBitmap::kARGB_8888_Config,
                      resource->size.width(),
                      resource->size.height());
        src.setPixels(resource->pixelBuffer);

        ScopedWriteLockSoftware lock(this, id);
        SkCanvas* dest = lock.skCanvas();
        dest->writePixels(src, 0, 0);
    }
}

void ResourceProvider::bindForSampling(ResourceProvider::ResourceId resourceId, GLenum target, GLenum filter)
{
    DCHECK(m_threadChecker.CalledOnValidThread());
    WebGraphicsContext3D* context3d = m_outputSurface->Context3D();
    ResourceMap::iterator it = m_resources.find(resourceId);
    DCHECK(it != m_resources.end());
    Resource* resource = &it->second;
    DCHECK(resource->lockForReadCount);
    DCHECK(!resource->lockedForWrite);

    GLC(context3d, context3d->bindTexture(target, resource->glId));
    if (filter != resource->filter) {
        GLC(context3d, context3d->texParameteri(target, GL_TEXTURE_MIN_FILTER, filter));
        GLC(context3d, context3d->texParameteri(target, GL_TEXTURE_MAG_FILTER, filter));
        resource->filter = filter;
    }
}

void ResourceProvider::beginSetPixels(ResourceId id)
{
    DCHECK(m_threadChecker.CalledOnValidThread());
    ResourceMap::iterator it = m_resources.find(id);
    CHECK(it != m_resources.end());
    Resource* resource = &it->second;
    DCHECK(!resource->pendingSetPixels);

    lockForWrite(id);

    if (resource->glId) {
        WebGraphicsContext3D* context3d = m_outputSurface->Context3D();
        DCHECK(context3d);
        DCHECK(resource->glPixelBufferId);
        context3d->bindTexture(GL_TEXTURE_2D, resource->glId);
        context3d->bindBuffer(
            GL_PIXEL_UNPACK_TRANSFER_BUFFER_CHROMIUM,
            resource->glPixelBufferId);
        if (!resource->glUploadQueryId)
            resource->glUploadQueryId = context3d->createQueryEXT();
        context3d->beginQueryEXT(
            GL_ASYNC_PIXEL_TRANSFERS_COMPLETED_CHROMIUM,
            resource->glUploadQueryId);
        context3d->asyncTexSubImage2DCHROMIUM(GL_TEXTURE_2D,
                                              0, /* level */
                                              0, /* x */
                                              0, /* y */
                                              resource->size.width(),
                                              resource->size.height(),
                                              resource->format,
                                              GL_UNSIGNED_BYTE,
                                              NULL);
        context3d->endQueryEXT(GL_ASYNC_PIXEL_TRANSFERS_COMPLETED_CHROMIUM);
        context3d->bindBuffer(GL_PIXEL_UNPACK_TRANSFER_BUFFER_CHROMIUM, 0);
    }

    if (resource->pixels)
      setPixelsFromBuffer(id);

    resource->pendingSetPixels = true;
}

bool ResourceProvider::didSetPixelsComplete(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->pendingSetPixels);

    if (resource->glId) {
        WebGraphicsContext3D* context3d = m_outputSurface->Context3D();
        DCHECK(context3d);
        DCHECK(resource->glUploadQueryId);
        unsigned complete = 1;
        context3d->getQueryObjectuivEXT(
            resource->glUploadQueryId,
            GL_QUERY_RESULT_AVAILABLE_EXT,
            &complete);
        if (!complete)
            return false;
    }

    resource->pendingSetPixels = false;
    unlockForWrite(id);

    return true;
}

}  // namespace cc