/*
 * Copyright (C) 2010, 2016 Apple Inc. All rights reserved.
 * Copyright (C) 2010 Google Inc. All rights reserved.
 * Copyright (C) 2010 Mozilla Corporation. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. 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 APPLE INC. ``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 APPLE INC. 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"

#if ENABLE(GRAPHICS_CONTEXT_3D)

#include "GraphicsContext3D.h"

#include "Extensions3D.h"
#include "FormatConverter.h"
#include "Image.h"
#include "ImageData.h"
#include "ImageObserver.h"

namespace WebCore {

namespace {

GraphicsContext3D::DataFormat getDataFormat(GC3Denum destinationFormat, GC3Denum destinationType)
{
    GraphicsContext3D::DataFormat dstFormat = GraphicsContext3D::DataFormatRGBA8;
    switch (destinationType) {
    case GraphicsContext3D::UNSIGNED_BYTE:
        switch (destinationFormat) {
        case GraphicsContext3D::RGB:
            dstFormat = GraphicsContext3D::DataFormatRGB8;
            break;
        case GraphicsContext3D::RGBA:
            dstFormat = GraphicsContext3D::DataFormatRGBA8;
            break;
        case GraphicsContext3D::ALPHA:
            dstFormat = GraphicsContext3D::DataFormatA8;
            break;
        case GraphicsContext3D::LUMINANCE:
            dstFormat = GraphicsContext3D::DataFormatR8;
            break;
        case GraphicsContext3D::LUMINANCE_ALPHA:
            dstFormat = GraphicsContext3D::DataFormatRA8;
            break;
        case GraphicsContext3D::SRGB:
            dstFormat = GraphicsContext3D::DataFormatRGB8;
            break;
        case GraphicsContext3D::SRGB_ALPHA:
            dstFormat = GraphicsContext3D::DataFormatRGBA8;
            break;
        default:
            ASSERT_NOT_REACHED();
        }
        break;
    case GraphicsContext3D::UNSIGNED_SHORT_4_4_4_4:
        dstFormat = GraphicsContext3D::DataFormatRGBA4444;
        break;
    case GraphicsContext3D::UNSIGNED_SHORT_5_5_5_1:
        dstFormat = GraphicsContext3D::DataFormatRGBA5551;
        break;
    case GraphicsContext3D::UNSIGNED_SHORT_5_6_5:
        dstFormat = GraphicsContext3D::DataFormatRGB565;
        break;
    case GraphicsContext3D::HALF_FLOAT_OES: // OES_texture_half_float
        switch (destinationFormat) {
        case GraphicsContext3D::RGB:
            dstFormat = GraphicsContext3D::DataFormatRGB16F;
            break;
        case GraphicsContext3D::RGBA:
            dstFormat = GraphicsContext3D::DataFormatRGBA16F;
            break;
        case GraphicsContext3D::ALPHA:
            dstFormat = GraphicsContext3D::DataFormatA16F;
            break;
        case GraphicsContext3D::LUMINANCE:
            dstFormat = GraphicsContext3D::DataFormatR16F;
            break;
        case GraphicsContext3D::LUMINANCE_ALPHA:
            dstFormat = GraphicsContext3D::DataFormatRA16F;
            break;
        case GraphicsContext3D::SRGB:
            dstFormat = GraphicsContext3D::DataFormatRGB16F;
            break;
        case GraphicsContext3D::SRGB_ALPHA:
            dstFormat = GraphicsContext3D::DataFormatRGBA16F;
            break;
        default:
            ASSERT_NOT_REACHED();
        }
        break;
    case GraphicsContext3D::FLOAT: // OES_texture_float
        switch (destinationFormat) {
        case GraphicsContext3D::RGB:
            dstFormat = GraphicsContext3D::DataFormatRGB32F;
            break;
        case GraphicsContext3D::RGBA:
            dstFormat = GraphicsContext3D::DataFormatRGBA32F;
            break;
        case GraphicsContext3D::ALPHA:
            dstFormat = GraphicsContext3D::DataFormatA32F;
            break;
        case GraphicsContext3D::LUMINANCE:
            dstFormat = GraphicsContext3D::DataFormatR32F;
            break;
        case GraphicsContext3D::LUMINANCE_ALPHA:
            dstFormat = GraphicsContext3D::DataFormatRA32F;
            break;
        case GraphicsContext3D::SRGB:
            dstFormat = GraphicsContext3D::DataFormatRGB32F;
            break;
        case GraphicsContext3D::SRGB_ALPHA:
            dstFormat = GraphicsContext3D::DataFormatRGBA32F;
            break;
        default:
            ASSERT_NOT_REACHED();
        }
        break;
    default:
        ASSERT_NOT_REACHED();
    }
    return dstFormat;
}

} // anonymous namespace

bool GraphicsContext3D::texImage2DResourceSafe(GC3Denum target, GC3Dint level, GC3Denum internalformat, GC3Dsizei width, GC3Dsizei height, GC3Dint border, GC3Denum format, GC3Denum type, GC3Dint unpackAlignment)
{
    ASSERT(unpackAlignment == 1 || unpackAlignment == 2 || unpackAlignment == 4 || unpackAlignment == 8);
    std::unique_ptr<unsigned char[]> zero;
    if (width > 0 && height > 0) {
        unsigned int size;
        GC3Denum error = computeImageSizeInBytes(format, type, width, height, unpackAlignment, &size, nullptr);
        if (error != GraphicsContext3D::NO_ERROR) {
            synthesizeGLError(error);
            return false;
        }
        zero = std::make_unique<unsigned char[]>(size);
        if (!zero) {
            synthesizeGLError(GraphicsContext3D::INVALID_VALUE);
            return false;
        }
        memset(zero.get(), 0, size);
    }
    return texImage2D(target, level, internalformat, width, height, border, format, type, zero.get());
}

bool GraphicsContext3D::computeFormatAndTypeParameters(GC3Denum format, GC3Denum type, unsigned int* componentsPerPixel, unsigned int* bytesPerComponent)
{
    switch (format) {
    case GraphicsContext3D::RED:
    case GraphicsContext3D::RED_INTEGER:
    case GraphicsContext3D::ALPHA:
    case GraphicsContext3D::LUMINANCE:
    case GraphicsContext3D::DEPTH_COMPONENT:
    case GraphicsContext3D::DEPTH_STENCIL:
        *componentsPerPixel = 1;
        break;
    case GraphicsContext3D::RG:
    case GraphicsContext3D::RG_INTEGER:
    case GraphicsContext3D::LUMINANCE_ALPHA:
        *componentsPerPixel = 2;
        break;
    case GraphicsContext3D::RGB:
    case GraphicsContext3D::RGB_INTEGER:
    case Extensions3D::SRGB_EXT:
        *componentsPerPixel = 3;
        break;
    case GraphicsContext3D::RGBA:
    case GraphicsContext3D::RGBA_INTEGER:
    case Extensions3D::BGRA_EXT: // GL_EXT_texture_format_BGRA8888
    case Extensions3D::SRGB_ALPHA_EXT:
        *componentsPerPixel = 4;
        break;
    default:
        return false;
    }

    switch (type) {
    case GraphicsContext3D::UNSIGNED_BYTE:
        *bytesPerComponent = sizeof(GC3Dubyte);
        break;
    case GraphicsContext3D::BYTE:
        *bytesPerComponent = sizeof(GC3Dbyte);
        break;
    case GraphicsContext3D::UNSIGNED_SHORT:
        *bytesPerComponent = sizeof(GC3Dushort);
        break;
    case GraphicsContext3D::SHORT:
        *bytesPerComponent = sizeof(GC3Dshort);
        break;
    case GraphicsContext3D::UNSIGNED_SHORT_5_6_5:
    case GraphicsContext3D::UNSIGNED_SHORT_4_4_4_4:
    case GraphicsContext3D::UNSIGNED_SHORT_5_5_5_1:
        *componentsPerPixel = 1;
        *bytesPerComponent = sizeof(GC3Dushort);
        break;
    case GraphicsContext3D::UNSIGNED_INT_24_8:
    case GraphicsContext3D::UNSIGNED_INT_2_10_10_10_REV:
    case GraphicsContext3D::UNSIGNED_INT_10F_11F_11F_REV:
    case GraphicsContext3D::UNSIGNED_INT_5_9_9_9_REV:
        *componentsPerPixel = 1;
        *bytesPerComponent = sizeof(GC3Duint);
        break;
    case GraphicsContext3D::UNSIGNED_INT:
        *bytesPerComponent = sizeof(GC3Duint);
        break;
    case GraphicsContext3D::INT:
        *bytesPerComponent = sizeof(GC3Dint);
        break;
    case GraphicsContext3D::FLOAT: // OES_texture_float
        *bytesPerComponent = sizeof(GC3Dfloat);
        break;
    case GraphicsContext3D::HALF_FLOAT:
    case GraphicsContext3D::HALF_FLOAT_OES: // OES_texture_half_float
        *bytesPerComponent = sizeof(GC3Dhalffloat);
        break;
    case GraphicsContext3D::FLOAT_32_UNSIGNED_INT_24_8_REV:
        *bytesPerComponent = sizeof(GC3Dfloat) + sizeof(GC3Duint);
        break;
    default:
        return false;
    }
    return true;
}

bool GraphicsContext3D::possibleFormatAndTypeForInternalFormat(GC3Denum internalFormat, GC3Denum& format, GC3Denum& type)
{
#define POSSIBLE_FORMAT_TYPE_CASE(internalFormatMacro, formatMacro, typeMacro) case internalFormatMacro: \
        format = formatMacro; \
        type = GraphicsContext3D::typeMacro; \
        break;

    switch (internalFormat) {
    POSSIBLE_FORMAT_TYPE_CASE(GraphicsContext3D::RGB               , GraphicsContext3D::RGB            , UNSIGNED_BYTE                 );
    POSSIBLE_FORMAT_TYPE_CASE(GraphicsContext3D::RGBA              , GraphicsContext3D::RGBA           , UNSIGNED_BYTE                 );
    POSSIBLE_FORMAT_TYPE_CASE(GraphicsContext3D::LUMINANCE_ALPHA   , GraphicsContext3D::LUMINANCE_ALPHA, UNSIGNED_BYTE                 );
    POSSIBLE_FORMAT_TYPE_CASE(GraphicsContext3D::LUMINANCE         , GraphicsContext3D::LUMINANCE      , UNSIGNED_BYTE                 );
    POSSIBLE_FORMAT_TYPE_CASE(GraphicsContext3D::ALPHA             , GraphicsContext3D::ALPHA          , UNSIGNED_BYTE                 );
    POSSIBLE_FORMAT_TYPE_CASE(GraphicsContext3D::R8                , GraphicsContext3D::RED            , UNSIGNED_BYTE                 );
    POSSIBLE_FORMAT_TYPE_CASE(GraphicsContext3D::R8_SNORM          , GraphicsContext3D::RED            , BYTE                          );
    POSSIBLE_FORMAT_TYPE_CASE(GraphicsContext3D::R16F              , GraphicsContext3D::RED            , HALF_FLOAT                    );
    POSSIBLE_FORMAT_TYPE_CASE(GraphicsContext3D::R32F              , GraphicsContext3D::RED            , FLOAT                         );
    POSSIBLE_FORMAT_TYPE_CASE(GraphicsContext3D::R8UI              , GraphicsContext3D::RED_INTEGER    , UNSIGNED_BYTE                 );
    POSSIBLE_FORMAT_TYPE_CASE(GraphicsContext3D::R8I               , GraphicsContext3D::RED_INTEGER    , BYTE                          );
    POSSIBLE_FORMAT_TYPE_CASE(GraphicsContext3D::R16UI             , GraphicsContext3D::RED_INTEGER    , UNSIGNED_SHORT                );
    POSSIBLE_FORMAT_TYPE_CASE(GraphicsContext3D::R16I              , GraphicsContext3D::RED_INTEGER    , SHORT                         );
    POSSIBLE_FORMAT_TYPE_CASE(GraphicsContext3D::R32UI             , GraphicsContext3D::RED_INTEGER    , UNSIGNED_INT                  );
    POSSIBLE_FORMAT_TYPE_CASE(GraphicsContext3D::R32I              , GraphicsContext3D::RED_INTEGER    , INT                           );
    POSSIBLE_FORMAT_TYPE_CASE(GraphicsContext3D::RG8               , GraphicsContext3D::RG             , UNSIGNED_BYTE                 );
    POSSIBLE_FORMAT_TYPE_CASE(GraphicsContext3D::RG8_SNORM         , GraphicsContext3D::RG             , BYTE                          );
    POSSIBLE_FORMAT_TYPE_CASE(GraphicsContext3D::RG16F             , GraphicsContext3D::RG             , HALF_FLOAT                    );
    POSSIBLE_FORMAT_TYPE_CASE(GraphicsContext3D::RG32F             , GraphicsContext3D::RG             , FLOAT                         );
    POSSIBLE_FORMAT_TYPE_CASE(GraphicsContext3D::RG8UI             , GraphicsContext3D::RG_INTEGER     , UNSIGNED_BYTE                 );
    POSSIBLE_FORMAT_TYPE_CASE(GraphicsContext3D::RG8I              , GraphicsContext3D::RG_INTEGER     , BYTE                          );
    POSSIBLE_FORMAT_TYPE_CASE(GraphicsContext3D::RG16UI            , GraphicsContext3D::RG_INTEGER     , UNSIGNED_SHORT                );
    POSSIBLE_FORMAT_TYPE_CASE(GraphicsContext3D::RG16I             , GraphicsContext3D::RG_INTEGER     , SHORT                         );
    POSSIBLE_FORMAT_TYPE_CASE(GraphicsContext3D::RG32UI            , GraphicsContext3D::RG_INTEGER     , UNSIGNED_INT                  );
    POSSIBLE_FORMAT_TYPE_CASE(GraphicsContext3D::RG32I             , GraphicsContext3D::RG_INTEGER     , INT                           );
    POSSIBLE_FORMAT_TYPE_CASE(GraphicsContext3D::RGB8              , GraphicsContext3D::RGB            , UNSIGNED_BYTE                 );
    POSSIBLE_FORMAT_TYPE_CASE(GraphicsContext3D::SRGB8             , GraphicsContext3D::RGB            , UNSIGNED_BYTE                 );
    POSSIBLE_FORMAT_TYPE_CASE(GraphicsContext3D::RGB565            , GraphicsContext3D::RGB            , UNSIGNED_SHORT_5_6_5          );
    POSSIBLE_FORMAT_TYPE_CASE(GraphicsContext3D::RGB8_SNORM        , GraphicsContext3D::RGB            , BYTE                          );
    POSSIBLE_FORMAT_TYPE_CASE(GraphicsContext3D::R11F_G11F_B10F    , GraphicsContext3D::RGB            , UNSIGNED_INT_10F_11F_11F_REV  );
    POSSIBLE_FORMAT_TYPE_CASE(GraphicsContext3D::RGB9_E5           , GraphicsContext3D::RGB            , UNSIGNED_INT_5_9_9_9_REV      );
    POSSIBLE_FORMAT_TYPE_CASE(GraphicsContext3D::RGB16F            , GraphicsContext3D::RGB            , HALF_FLOAT                    );
    POSSIBLE_FORMAT_TYPE_CASE(GraphicsContext3D::RGB32F            , GraphicsContext3D::RGB            , FLOAT                         );
    POSSIBLE_FORMAT_TYPE_CASE(GraphicsContext3D::RGB8UI            , GraphicsContext3D::RGB_INTEGER    , UNSIGNED_BYTE                 );
    POSSIBLE_FORMAT_TYPE_CASE(GraphicsContext3D::RGB8I             , GraphicsContext3D::RGB_INTEGER    , BYTE                          );
    POSSIBLE_FORMAT_TYPE_CASE(GraphicsContext3D::RGB16UI           , GraphicsContext3D::RGB_INTEGER    , UNSIGNED_SHORT                );
    POSSIBLE_FORMAT_TYPE_CASE(GraphicsContext3D::RGB16I            , GraphicsContext3D::RGB_INTEGER    , SHORT                         );
    POSSIBLE_FORMAT_TYPE_CASE(GraphicsContext3D::RGB32UI           , GraphicsContext3D::RGB_INTEGER    , UNSIGNED_INT                  );
    POSSIBLE_FORMAT_TYPE_CASE(GraphicsContext3D::RGB32I            , GraphicsContext3D::RGB_INTEGER    , INT                           );
    POSSIBLE_FORMAT_TYPE_CASE(GraphicsContext3D::RGBA8             , GraphicsContext3D::RGBA           , UNSIGNED_BYTE                 );
    POSSIBLE_FORMAT_TYPE_CASE(GraphicsContext3D::SRGB8_ALPHA8      , GraphicsContext3D::RGBA           , UNSIGNED_BYTE                 );
    POSSIBLE_FORMAT_TYPE_CASE(GraphicsContext3D::RGBA8_SNORM       , GraphicsContext3D::RGBA           , BYTE                          );
    POSSIBLE_FORMAT_TYPE_CASE(GraphicsContext3D::RGB5_A1           , GraphicsContext3D::RGBA           , UNSIGNED_SHORT_5_5_5_1        );
    POSSIBLE_FORMAT_TYPE_CASE(GraphicsContext3D::RGBA4             , GraphicsContext3D::RGBA           , UNSIGNED_SHORT_4_4_4_4        );
    POSSIBLE_FORMAT_TYPE_CASE(GraphicsContext3D::RGB10_A2          , GraphicsContext3D::RGBA           , UNSIGNED_INT_2_10_10_10_REV   );
    POSSIBLE_FORMAT_TYPE_CASE(GraphicsContext3D::RGBA16F           , GraphicsContext3D::RGBA           , HALF_FLOAT                    );
    POSSIBLE_FORMAT_TYPE_CASE(GraphicsContext3D::RGBA32F           , GraphicsContext3D::RGBA           , FLOAT                         );
    POSSIBLE_FORMAT_TYPE_CASE(GraphicsContext3D::RGBA8UI           , GraphicsContext3D::RGBA_INTEGER   , UNSIGNED_BYTE                 );
    POSSIBLE_FORMAT_TYPE_CASE(GraphicsContext3D::RGBA8I            , GraphicsContext3D::RGBA_INTEGER   , BYTE                          );
    POSSIBLE_FORMAT_TYPE_CASE(GraphicsContext3D::RGB10_A2UI        , GraphicsContext3D::RGBA_INTEGER   , UNSIGNED_INT_2_10_10_10_REV   );
    POSSIBLE_FORMAT_TYPE_CASE(GraphicsContext3D::RGBA16UI          , GraphicsContext3D::RGBA_INTEGER   , UNSIGNED_SHORT                );
    POSSIBLE_FORMAT_TYPE_CASE(GraphicsContext3D::RGBA16I           , GraphicsContext3D::RGBA_INTEGER   , SHORT                         );
    POSSIBLE_FORMAT_TYPE_CASE(GraphicsContext3D::RGBA32I           , GraphicsContext3D::RGBA_INTEGER   , INT                           );
    POSSIBLE_FORMAT_TYPE_CASE(GraphicsContext3D::RGBA32UI          , GraphicsContext3D::RGBA_INTEGER   , UNSIGNED_INT                  );
    POSSIBLE_FORMAT_TYPE_CASE(GraphicsContext3D::DEPTH_COMPONENT16 , GraphicsContext3D::DEPTH_COMPONENT, UNSIGNED_SHORT                );
    POSSIBLE_FORMAT_TYPE_CASE(GraphicsContext3D::DEPTH_COMPONENT   , GraphicsContext3D::DEPTH_COMPONENT, UNSIGNED_SHORT                );
    POSSIBLE_FORMAT_TYPE_CASE(GraphicsContext3D::DEPTH_COMPONENT24 , GraphicsContext3D::DEPTH_COMPONENT, UNSIGNED_INT                  );
    POSSIBLE_FORMAT_TYPE_CASE(GraphicsContext3D::DEPTH_COMPONENT32F, GraphicsContext3D::DEPTH_COMPONENT, FLOAT                         );
    POSSIBLE_FORMAT_TYPE_CASE(GraphicsContext3D::DEPTH_STENCIL     , GraphicsContext3D::DEPTH_STENCIL  , UNSIGNED_INT_24_8             );
    POSSIBLE_FORMAT_TYPE_CASE(GraphicsContext3D::DEPTH24_STENCIL8  , GraphicsContext3D::DEPTH_STENCIL  , UNSIGNED_INT_24_8             );
    POSSIBLE_FORMAT_TYPE_CASE(GraphicsContext3D::DEPTH32F_STENCIL8 , GraphicsContext3D::DEPTH_STENCIL  , FLOAT_32_UNSIGNED_INT_24_8_REV);
    POSSIBLE_FORMAT_TYPE_CASE(Extensions3D::SRGB_EXT               , Extensions3D::SRGB_EXT            , UNSIGNED_BYTE                 );
    POSSIBLE_FORMAT_TYPE_CASE(Extensions3D::SRGB_ALPHA_EXT         , Extensions3D::SRGB_ALPHA_EXT      , UNSIGNED_BYTE                 );
    default:
        return false;
    }
#undef POSSIBLE_FORMAT_TYPE_CASE

    return true;
}

GC3Denum GraphicsContext3D::computeImageSizeInBytes(GC3Denum format, GC3Denum type, GC3Dsizei width, GC3Dsizei height, GC3Dint alignment, unsigned int* imageSizeInBytes, unsigned int* paddingInBytes)
{
    ASSERT(imageSizeInBytes);
    ASSERT(alignment == 1 || alignment == 2 || alignment == 4 || alignment == 8);
    if (width < 0 || height < 0)
        return GraphicsContext3D::INVALID_VALUE;
    unsigned int bytesPerComponent, componentsPerPixel;
    if (!computeFormatAndTypeParameters(format, type, &componentsPerPixel, &bytesPerComponent))
        return GraphicsContext3D::INVALID_ENUM;
    if (!width || !height) {
        *imageSizeInBytes = 0;
        if (paddingInBytes)
            *paddingInBytes = 0;
        return GraphicsContext3D::NO_ERROR;
    }
    Checked<uint32_t, RecordOverflow> checkedValue = bytesPerComponent * componentsPerPixel;
    checkedValue *=  width;
    if (checkedValue.hasOverflowed())
        return GraphicsContext3D::INVALID_VALUE;
    unsigned int validRowSize = checkedValue.unsafeGet();
    unsigned int padding = 0;
    unsigned int residual = validRowSize % alignment;
    if (residual) {
        padding = alignment - residual;
        checkedValue += padding;
    }
    // Last row needs no padding.
    checkedValue *= (height - 1);
    checkedValue += validRowSize;
    if (checkedValue.hasOverflowed())
        return GraphicsContext3D::INVALID_VALUE;
    *imageSizeInBytes = checkedValue.unsafeGet();
    if (paddingInBytes)
        *paddingInBytes = padding;
    return GraphicsContext3D::NO_ERROR;
}

GraphicsContext3D::ImageExtractor::ImageExtractor(Image* image, ImageHtmlDomSource imageHtmlDomSource, bool premultiplyAlpha, bool ignoreGammaAndColorProfile)
{
    m_image = image;
    m_imageHtmlDomSource = imageHtmlDomSource;
    m_extractSucceeded = extractImage(premultiplyAlpha, ignoreGammaAndColorProfile);
}

bool GraphicsContext3D::packImageData(Image* image, const void* pixels, GC3Denum format, GC3Denum type, bool flipY, AlphaOp alphaOp, DataFormat sourceFormat, unsigned width, unsigned height, unsigned sourceUnpackAlignment, Vector<uint8_t>& data)
{
    if (!image || !pixels)
        return false;

    unsigned packedSize;
    // Output data is tightly packed (alignment == 1).
    if (computeImageSizeInBytes(format, type, width, height, 1, &packedSize, nullptr) != GraphicsContext3D::NO_ERROR)
        return false;
    data.resize(packedSize);

    if (!packPixels(reinterpret_cast<const uint8_t*>(pixels), sourceFormat, width, height, sourceUnpackAlignment, format, type, alphaOp, data.data(), flipY))
        return false;
    if (ImageObserver* observer = image->imageObserver())
        observer->didDraw(*image);
    return true;
}

bool GraphicsContext3D::extractImageData(ImageData* imageData, GC3Denum format, GC3Denum type, bool flipY, bool premultiplyAlpha, Vector<uint8_t>& data)
{
    if (!imageData)
        return false;
    int width = imageData->width();
    int height = imageData->height();

    unsigned int packedSize;
    // Output data is tightly packed (alignment == 1).
    if (computeImageSizeInBytes(format, type, width, height, 1, &packedSize, nullptr) != GraphicsContext3D::NO_ERROR)
        return false;
    data.resize(packedSize);

    if (!packPixels(imageData->data()->data(), DataFormatRGBA8, width, height, 0, format, type, premultiplyAlpha ? AlphaDoPremultiply : AlphaDoNothing, data.data(), flipY))
        return false;

    return true;
}

bool GraphicsContext3D::extractTextureData(unsigned int width, unsigned int height, GC3Denum format, GC3Denum type, unsigned int unpackAlignment, bool flipY, bool premultiplyAlpha, const void* pixels, Vector<uint8_t>& data)
{
    // Assumes format, type, etc. have already been validated.
    DataFormat sourceDataFormat = getDataFormat(format, type);

    // Resize the output buffer.
    unsigned int componentsPerPixel, bytesPerComponent;
    if (!computeFormatAndTypeParameters(format, type, &componentsPerPixel, &bytesPerComponent))
        return false;
    unsigned int bytesPerPixel = componentsPerPixel * bytesPerComponent;
    data.resize(width * height * bytesPerPixel);

    if (!packPixels(static_cast<const uint8_t*>(pixels), sourceDataFormat, width, height, unpackAlignment, format, type, (premultiplyAlpha ? AlphaDoPremultiply : AlphaDoNothing), data.data(), flipY))
        return false;

    return true;
}

ALWAYS_INLINE unsigned TexelBytesForFormat(GraphicsContext3D::DataFormat format)
{
    switch (format) {
    case GraphicsContext3D::DataFormatR8:
    case GraphicsContext3D::DataFormatA8:
        return 1;
    case GraphicsContext3D::DataFormatRA8:
    case GraphicsContext3D::DataFormatAR8:
    case GraphicsContext3D::DataFormatRGBA5551:
    case GraphicsContext3D::DataFormatRGBA4444:
    case GraphicsContext3D::DataFormatRGB565:
    case GraphicsContext3D::DataFormatA16F:
    case GraphicsContext3D::DataFormatR16F:
        return 2;
    case GraphicsContext3D::DataFormatRGB8:
    case GraphicsContext3D::DataFormatBGR8:
        return 3;
    case GraphicsContext3D::DataFormatRGBA8:
    case GraphicsContext3D::DataFormatARGB8:
    case GraphicsContext3D::DataFormatABGR8:
    case GraphicsContext3D::DataFormatBGRA8:
    case GraphicsContext3D::DataFormatR32F:
    case GraphicsContext3D::DataFormatA32F:
    case GraphicsContext3D::DataFormatRA16F:
        return 4;
    case GraphicsContext3D::DataFormatRGB16F:
        return 6;
    case GraphicsContext3D::DataFormatRA32F:
    case GraphicsContext3D::DataFormatRGBA16F:
        return 8;
    case GraphicsContext3D::DataFormatRGB32F:
        return 12;
    case GraphicsContext3D::DataFormatRGBA32F:
        return 16;
    default:
        return 0;
    }
}

bool GraphicsContext3D::packPixels(const uint8_t* sourceData, DataFormat sourceDataFormat, unsigned width, unsigned height, unsigned sourceUnpackAlignment, unsigned destinationFormat, unsigned destinationType, AlphaOp alphaOp, void* destinationData, bool flipY)
{
    int validSrc = width * TexelBytesForFormat(sourceDataFormat);
    int remainder = sourceUnpackAlignment ? (validSrc % sourceUnpackAlignment) : 0;
    int srcStride = remainder ? (validSrc + sourceUnpackAlignment - remainder) : validSrc;

    // FIXME: Implement packing pixels to WebGL 2 formats
    switch (destinationFormat) {
    case GraphicsContext3D::RED:
    case GraphicsContext3D::RED_INTEGER:
    case GraphicsContext3D::RG:
    case GraphicsContext3D::RG_INTEGER:
    case GraphicsContext3D::RGB_INTEGER:
    case GraphicsContext3D::RGBA_INTEGER:
    case GraphicsContext3D::DEPTH_COMPONENT:
    case GraphicsContext3D::DEPTH_STENCIL:
        return false;
    }
    switch (destinationType) {
    case GraphicsContext3D::BYTE:
    case GraphicsContext3D::SHORT:
    case GraphicsContext3D::INT:
    case GraphicsContext3D::UNSIGNED_INT_2_10_10_10_REV:
    case GraphicsContext3D::UNSIGNED_INT_10F_11F_11F_REV:
    case GraphicsContext3D::UNSIGNED_INT_5_9_9_9_REV:
    case GraphicsContext3D::UNSIGNED_INT_24_8:
        return false;
    }

    DataFormat dstDataFormat = getDataFormat(destinationFormat, destinationType);
    int dstStride = width * TexelBytesForFormat(dstDataFormat);
    if (flipY) {
        destinationData = static_cast<uint8_t*>(destinationData) + dstStride*(height - 1);
        dstStride = -dstStride;
    }
    if (!hasAlpha(sourceDataFormat) || !hasColor(sourceDataFormat) || !hasColor(dstDataFormat))
        alphaOp = AlphaDoNothing;

    if (sourceDataFormat == dstDataFormat && alphaOp == AlphaDoNothing) {
        const uint8_t* ptr = sourceData;
        const uint8_t* ptrEnd = sourceData + srcStride * height;
        unsigned rowSize = (dstStride > 0) ? dstStride: -dstStride;
        uint8_t* dst = static_cast<uint8_t*>(destinationData);
        while (ptr < ptrEnd) {
            memcpy(dst, ptr, rowSize);
            ptr += srcStride;
            dst += dstStride;
        }
        return true;
    }

    FormatConverter converter(width, height, sourceData, destinationData, srcStride, dstStride);
    converter.convert(sourceDataFormat, dstDataFormat, alphaOp);
    if (!converter.success())
        return false;
    return true;
}

unsigned GraphicsContext3D::getClearBitsByAttachmentType(GC3Denum attachment)
{
    switch (attachment) {
    case GraphicsContext3D::COLOR_ATTACHMENT0:
    case Extensions3D::COLOR_ATTACHMENT1_EXT:
    case Extensions3D::COLOR_ATTACHMENT2_EXT:
    case Extensions3D::COLOR_ATTACHMENT3_EXT:
    case Extensions3D::COLOR_ATTACHMENT4_EXT:
    case Extensions3D::COLOR_ATTACHMENT5_EXT:
    case Extensions3D::COLOR_ATTACHMENT6_EXT:
    case Extensions3D::COLOR_ATTACHMENT7_EXT:
    case Extensions3D::COLOR_ATTACHMENT8_EXT:
    case Extensions3D::COLOR_ATTACHMENT9_EXT:
    case Extensions3D::COLOR_ATTACHMENT10_EXT:
    case Extensions3D::COLOR_ATTACHMENT11_EXT:
    case Extensions3D::COLOR_ATTACHMENT12_EXT:
    case Extensions3D::COLOR_ATTACHMENT13_EXT:
    case Extensions3D::COLOR_ATTACHMENT14_EXT:
    case Extensions3D::COLOR_ATTACHMENT15_EXT:
        return GraphicsContext3D::COLOR_BUFFER_BIT;
    case GraphicsContext3D::DEPTH_ATTACHMENT:
        return GraphicsContext3D::DEPTH_BUFFER_BIT;
    case GraphicsContext3D::STENCIL_ATTACHMENT:
        return GraphicsContext3D::STENCIL_BUFFER_BIT;
    case GraphicsContext3D::DEPTH_STENCIL_ATTACHMENT:
        return GraphicsContext3D::DEPTH_BUFFER_BIT | GraphicsContext3D::STENCIL_BUFFER_BIT;
    default:
        return 0;
    }
}

unsigned GraphicsContext3D::getClearBitsByFormat(GC3Denum format)
{
    switch (format) {
    case GraphicsContext3D::RGB:
    case GraphicsContext3D::RGBA:
    case GraphicsContext3D::LUMINANCE_ALPHA:
    case GraphicsContext3D::LUMINANCE:
    case GraphicsContext3D::ALPHA:
    case GraphicsContext3D::R8:
    case GraphicsContext3D::R8_SNORM:
    case GraphicsContext3D::R16F:
    case GraphicsContext3D::R32F:
    case GraphicsContext3D::R8UI:
    case GraphicsContext3D::R8I:
    case GraphicsContext3D::R16UI:
    case GraphicsContext3D::R16I:
    case GraphicsContext3D::R32UI:
    case GraphicsContext3D::R32I:
    case GraphicsContext3D::RG8:
    case GraphicsContext3D::RG8_SNORM:
    case GraphicsContext3D::RG16F:
    case GraphicsContext3D::RG32F:
    case GraphicsContext3D::RG8UI:
    case GraphicsContext3D::RG8I:
    case GraphicsContext3D::RG16UI:
    case GraphicsContext3D::RG16I:
    case GraphicsContext3D::RG32UI:
    case GraphicsContext3D::RG32I:
    case GraphicsContext3D::RGB8:
    case GraphicsContext3D::SRGB8:
    case GraphicsContext3D::RGB565:
    case GraphicsContext3D::RGB8_SNORM:
    case GraphicsContext3D::R11F_G11F_B10F:
    case GraphicsContext3D::RGB9_E5:
    case GraphicsContext3D::RGB16F:
    case GraphicsContext3D::RGB32F:
    case GraphicsContext3D::RGB8UI:
    case GraphicsContext3D::RGB8I:
    case GraphicsContext3D::RGB16UI:
    case GraphicsContext3D::RGB16I:
    case GraphicsContext3D::RGB32UI:
    case GraphicsContext3D::RGB32I:
    case GraphicsContext3D::RGBA8:
    case GraphicsContext3D::SRGB8_ALPHA8:
    case GraphicsContext3D::RGBA8_SNORM:
    case GraphicsContext3D::RGB5_A1:
    case GraphicsContext3D::RGBA4:
    case GraphicsContext3D::RGB10_A2:
    case GraphicsContext3D::RGBA16F:
    case GraphicsContext3D::RGBA32F:
    case GraphicsContext3D::RGBA8UI:
    case GraphicsContext3D::RGBA8I:
    case GraphicsContext3D::RGB10_A2UI:
    case GraphicsContext3D::RGBA16UI:
    case GraphicsContext3D::RGBA16I:
    case GraphicsContext3D::RGBA32I:
    case GraphicsContext3D::RGBA32UI:
    case Extensions3D::SRGB_EXT:
    case Extensions3D::SRGB_ALPHA_EXT:
        return GraphicsContext3D::COLOR_BUFFER_BIT;
    case GraphicsContext3D::DEPTH_COMPONENT16:
    case GraphicsContext3D::DEPTH_COMPONENT24:
    case GraphicsContext3D::DEPTH_COMPONENT32F:
    case GraphicsContext3D::DEPTH_COMPONENT:
        return GraphicsContext3D::DEPTH_BUFFER_BIT;
    case GraphicsContext3D::STENCIL_INDEX8:
        return GraphicsContext3D::STENCIL_BUFFER_BIT;
    case GraphicsContext3D::DEPTH_STENCIL:
    case GraphicsContext3D::DEPTH24_STENCIL8:
    case GraphicsContext3D::DEPTH32F_STENCIL8:
        return GraphicsContext3D::DEPTH_BUFFER_BIT | GraphicsContext3D::STENCIL_BUFFER_BIT;
    default:
        return 0;
    }
}

unsigned GraphicsContext3D::getChannelBitsByFormat(GC3Denum format)
{
    switch (format) {
    case GraphicsContext3D::ALPHA:
        return ChannelAlpha;
    case GraphicsContext3D::LUMINANCE:
        return ChannelRGB;
    case GraphicsContext3D::LUMINANCE_ALPHA:
        return ChannelRGBA;
    case GraphicsContext3D::RGB:
    case GraphicsContext3D::RGB565:
    case Extensions3D::SRGB_EXT:
        return ChannelRGB;
    case GraphicsContext3D::RGBA:
    case GraphicsContext3D::RGBA4:
    case GraphicsContext3D::RGB5_A1:
    case Extensions3D::SRGB_ALPHA_EXT:
        return ChannelRGBA;
    case GraphicsContext3D::DEPTH_COMPONENT16:
    case GraphicsContext3D::DEPTH_COMPONENT:
        return ChannelDepth;
    case GraphicsContext3D::STENCIL_INDEX8:
        return ChannelStencil;
    case GraphicsContext3D::DEPTH_STENCIL:
        return ChannelDepth | ChannelStencil;
    default:
        return 0;
    }
}

#if !PLATFORM(MAC)
void GraphicsContext3D::setContextVisibility(bool)
{
}
#endif

#if !PLATFORM(COCOA)
void GraphicsContext3D::simulateContextChanged()
{
}
#endif

} // namespace WebCore

#endif // ENABLE(GRAPHICS_CONTEXT_3D)
