/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "WebGLTexture.h"

#include <algorithm>
#include "CanvasUtils.h"
#include "GLBlitHelper.h"
#include "GLContext.h"
#include "mozilla/dom/HTMLVideoElement.h"
#include "mozilla/dom/ImageData.h"
#include "mozilla/MathAlgorithms.h"
#include "mozilla/Scoped.h"
#include "ScopedGLHelpers.h"
#include "WebGLContext.h"
#include "WebGLContextUtils.h"
#include "WebGLFramebuffer.h"
#include "WebGLTexelConversions.h"

namespace mozilla {

bool
DoesTargetMatchDimensions(WebGLContext* webgl, TexImageTarget target, uint8_t funcDims,
                          const char* funcName)
{
    uint8_t targetDims;
    switch (target.get()) {
    case LOCAL_GL_TEXTURE_2D:
    case LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_X:
    case LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_X:
    case LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_Y:
    case LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Y:
    case LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_Z:
    case LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Z:
        targetDims = 2;
        break;

    case LOCAL_GL_TEXTURE_3D:
        targetDims = 3;
        break;

    default:
        MOZ_CRASH("Unhandled texImageTarget.");
    }

    if (targetDims != funcDims) {
        webgl->ErrorInvalidEnum("%s: `target` must match function dimensions.", funcName);
        return false;
    }

    return true;
}

void
WebGLTexture::CompressedTexImage2D(TexImageTarget texImageTarget,
                                   GLint level,
                                   GLenum internalFormat,
                                   GLsizei width, GLsizei height, GLint border,
                                   const dom::ArrayBufferView& view)
{
    const WebGLTexImageFunc func = WebGLTexImageFunc::CompTexImage;
    const WebGLTexDimensions dims = WebGLTexDimensions::Tex2D;

    const char funcName[] = "compressedTexImage2D";
    if (!DoesTargetMatchDimensions(mContext, texImageTarget, 2, funcName))
        return;

    if (!mContext->ValidateTexImage(texImageTarget.get(), level, internalFormat,
                          0, 0, 0, width, height, 0,
                          border, LOCAL_GL_NONE,
                          LOCAL_GL_NONE,
                          func, dims))
    {
        return;
    }

    view.ComputeLengthAndData();

    uint32_t byteLength = view.Length();
    if (!mContext->ValidateCompTexImageDataSize(level, internalFormat, width, height, byteLength, func, dims)) {
        return;
    }

    if (!mContext->ValidateCompTexImageSize(level, internalFormat, 0, 0, width, height, width, height, func, dims))
    {
        return;
    }

    if (mImmutable) {
        return mContext->ErrorInvalidOperation(
            "compressedTexImage2D: disallowed because the texture bound to "
            "this target has already been made immutable by texStorage2D");
    }

    mContext->MakeContextCurrent();
    gl::GLContext* gl = mContext->gl;
    gl->fCompressedTexImage2D(texImageTarget.get(), level, internalFormat, width, height, border, byteLength, view.Data());

    SetImageInfo(texImageTarget, level, width, height, 1, internalFormat,
                      WebGLImageDataStatus::InitializedImageData);
}

void
WebGLTexture::CompressedTexSubImage2D(TexImageTarget texImageTarget, GLint level, GLint xOffset,
                                      GLint yOffset, GLsizei width, GLsizei height,
                                      GLenum internalFormat,
                                      const dom::ArrayBufferView& view)
{
    const WebGLTexImageFunc func = WebGLTexImageFunc::CompTexSubImage;
    const WebGLTexDimensions dims = WebGLTexDimensions::Tex2D;

    const char funcName[] = "compressedTexSubImage2D";
    if (!DoesTargetMatchDimensions(mContext, texImageTarget, 2, funcName))
        return;

    if (!mContext->ValidateTexImage(texImageTarget.get(),
                          level, internalFormat,
                          xOffset, yOffset, 0,
                          width, height, 0,
                          0, LOCAL_GL_NONE, LOCAL_GL_NONE,
                          func, dims))
    {
        return;
    }

    WebGLTexture::ImageInfo& levelInfo = ImageInfoAt(texImageTarget, level);

    if (internalFormat != levelInfo.EffectiveInternalFormat()) {
        return mContext->ErrorInvalidOperation("compressedTexImage2D: internalFormat does not match the existing image");
    }

    view.ComputeLengthAndData();

    uint32_t byteLength = view.Length();
    if (!mContext->ValidateCompTexImageDataSize(level, internalFormat, width, height, byteLength, func, dims))
        return;

    if (!mContext->ValidateCompTexImageSize(level, internalFormat,
                                  xOffset, yOffset,
                                  width, height,
                                  levelInfo.Width(), levelInfo.Height(),
                                  func, dims))
    {
        return;
    }

    if (levelInfo.HasUninitializedImageData()) {
        bool coversWholeImage = xOffset == 0 &&
                                yOffset == 0 &&
                                width == levelInfo.Width() &&
                                height == levelInfo.Height();
        if (coversWholeImage) {
            SetImageDataStatus(texImageTarget, level, WebGLImageDataStatus::InitializedImageData);
        } else {
            if (!EnsureInitializedImageData(texImageTarget, level))
                return;
        }
    }

    mContext->MakeContextCurrent();
    gl::GLContext* gl = mContext->gl;
    gl->fCompressedTexSubImage2D(texImageTarget.get(), level, xOffset, yOffset, width, height, internalFormat, byteLength, view.Data());
}

void
WebGLTexture::CopyTexSubImage2D_base(TexImageTarget texImageTarget, GLint level,
                                     TexInternalFormat internalFormat,
                                     GLint xOffset, GLint yOffset, GLint x,
                                     GLint y, GLsizei width, GLsizei height,
                                     bool sub)
{
    const WebGLRectangleObject* framebufferRect = mContext->CurValidReadFBRectObject();
    GLsizei framebufferWidth = framebufferRect ? framebufferRect->Width() : 0;
    GLsizei framebufferHeight = framebufferRect ? framebufferRect->Height() : 0;

    WebGLTexImageFunc func = sub
                             ? WebGLTexImageFunc::CopyTexSubImage
                             : WebGLTexImageFunc::CopyTexImage;
    WebGLTexDimensions dims = WebGLTexDimensions::Tex2D;
    const char* info = InfoFrom(func, dims);

    // TODO: This changes with color_buffer_float. Reassess when the
    // patch lands.
    if (!mContext->ValidateTexImage(texImageTarget, level, internalFormat.get(),
                          xOffset, yOffset, 0,
                          width, height, 0,
                          0,
                          LOCAL_GL_NONE, LOCAL_GL_NONE,
                          func, dims))
    {
        return;
    }

    if (!mContext->ValidateCopyTexImage(internalFormat.get(), func, dims))
        return;

    if (!mContext->mBoundReadFramebuffer)
        mContext->ClearBackbufferIfNeeded();

    mContext->MakeContextCurrent();
    gl::GLContext* gl = mContext->gl;

    if (mImmutable) {
        if (!sub) {
            return mContext->ErrorInvalidOperation("copyTexImage2D: disallowed because the texture bound to this target has already been made immutable by texStorage2D");
        }
    }

    TexType framebuffertype = LOCAL_GL_NONE;
    if (mContext->mBoundReadFramebuffer) {
        TexInternalFormat framebuffereffectiveformat = mContext->mBoundReadFramebuffer->ColorAttachment(0).EffectiveInternalFormat();
        framebuffertype = TypeFromInternalFormat(framebuffereffectiveformat);
    } else {
        // FIXME - here we're assuming that the default framebuffer is backed by UNSIGNED_BYTE
        // that might not always be true, say if we had a 16bpp default framebuffer.
        framebuffertype = LOCAL_GL_UNSIGNED_BYTE;
    }

    TexInternalFormat effectiveInternalFormat =
        EffectiveInternalFormatFromUnsizedInternalFormatAndType(internalFormat, framebuffertype);

    // this should never fail, validation happened earlier.
    MOZ_ASSERT(effectiveInternalFormat != LOCAL_GL_NONE);

    const bool widthOrHeightIsZero = (width == 0 || height == 0);
    if (gl->WorkAroundDriverBugs() &&
        sub && widthOrHeightIsZero)
    {
        // NV driver on Linux complains that CopyTexSubImage2D(level=0,
        // xOffset=0, yOffset=2, x=0, y=0, width=0, height=0) from a 300x150 FB
        // to a 0x2 texture. This a useless thing to do, but technically legal.
        // NV331.38 generates INVALID_VALUE.
        return mContext->DummyFramebufferOperation(info);
    }

    // check if the memory size of this texture may change with this call
    bool sizeMayChange = !sub;
    if (!sub && HasImageInfoAt(texImageTarget, level)) {
        const WebGLTexture::ImageInfo& imageInfo = ImageInfoAt(texImageTarget, level);
        sizeMayChange = width != imageInfo.Width() ||
                        height != imageInfo.Height() ||
                        effectiveInternalFormat != imageInfo.EffectiveInternalFormat();
    }

    if (sizeMayChange)
        mContext->GetAndFlushUnderlyingGLErrors();

    if (CanvasUtils::CheckSaneSubrectSize(x, y, width, height, framebufferWidth, framebufferHeight)) {
        if (sub)
            gl->fCopyTexSubImage2D(texImageTarget.get(), level, xOffset, yOffset, x, y, width, height);
        else
            gl->fCopyTexImage2D(texImageTarget.get(), level, internalFormat.get(), x, y, width, height, 0);
    } else {

        // the rect doesn't fit in the framebuffer

        // first, we initialize the texture as black
        if (!sub) {
            SetImageInfo(texImageTarget, level, width, height, 1,
                      effectiveInternalFormat,
                      WebGLImageDataStatus::UninitializedImageData);
            if (!EnsureInitializedImageData(texImageTarget, level))
                return;
        }

        // if we are completely outside of the framebuffer, we can exit now with our black texture
        if (   x >= framebufferWidth
            || x+width <= 0
            || y >= framebufferHeight
            || y+height <= 0)
        {
            // we are completely outside of range, can exit now with buffer filled with zeros
            return mContext->DummyFramebufferOperation(info);
        }

        GLint   actual_x             = clamped(x, 0, framebufferWidth);
        GLint   actual_x_plus_width  = clamped(x + width, 0, framebufferWidth);
        GLsizei actual_width   = actual_x_plus_width  - actual_x;
        GLint   actual_xOffset = xOffset + actual_x - x;

        GLint   actual_y             = clamped(y, 0, framebufferHeight);
        GLint   actual_y_plus_height = clamped(y + height, 0, framebufferHeight);
        GLsizei actual_height  = actual_y_plus_height - actual_y;
        GLint   actual_yOffset = yOffset + actual_y - y;

        gl->fCopyTexSubImage2D(texImageTarget.get(), level, actual_xOffset, actual_yOffset, actual_x, actual_y, actual_width, actual_height);
    }

    if (sizeMayChange) {
        GLenum error = mContext->GetAndFlushUnderlyingGLErrors();
        if (error) {
            mContext->GenerateWarning("copyTexImage2D generated error %s", mContext->ErrorName(error));
            return;
        }
    }

    if (!sub) {
        SetImageInfo(texImageTarget, level, width, height, 1,
                          effectiveInternalFormat,
                          WebGLImageDataStatus::InitializedImageData);
    }
}

void
WebGLTexture::CopyTexImage2D(TexImageTarget texImageTarget,
                             GLint level,
                             GLenum internalFormat,
                             GLint x,
                             GLint y,
                             GLsizei width,
                             GLsizei height,
                             GLint border)
{
    // copyTexImage2D only generates textures with type = UNSIGNED_BYTE
    const WebGLTexImageFunc func = WebGLTexImageFunc::CopyTexImage;
    const WebGLTexDimensions dims = WebGLTexDimensions::Tex2D;

    const char funcName[] = "copyTexImage2D";
    if (!DoesTargetMatchDimensions(mContext, texImageTarget, 2, funcName))
        return;

    if (!mContext->ValidateTexImage(texImageTarget.get(), level, internalFormat,
                          0, 0, 0,
                          width, height, 0,
                          border, LOCAL_GL_NONE, LOCAL_GL_NONE,
                          func, dims))
    {
        return;
    }

    if (!mContext->ValidateCopyTexImage(internalFormat, func, dims))
        return;

    if (!mContext->mBoundReadFramebuffer)
        mContext->ClearBackbufferIfNeeded();

    CopyTexSubImage2D_base(texImageTarget, level, internalFormat, 0, 0, x, y, width, height, false);
}

void
WebGLTexture::CopyTexSubImage2D(TexImageTarget texImageTarget,
                                GLint level,
                                GLint xOffset,
                                GLint yOffset,
                                GLint x,
                                GLint y,
                                GLsizei width,
                                GLsizei height)
{
    switch (texImageTarget.get()) {
    case LOCAL_GL_TEXTURE_2D:
    case LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_X:
    case LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_X:
    case LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_Y:
    case LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Y:
    case LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_Z:
    case LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Z:
        break;
    default:
        return mContext->ErrorInvalidEnumInfo("copyTexSubImage2D: target", texImageTarget.get());
    }

    if (level < 0)
        return mContext->ErrorInvalidValue("copyTexSubImage2D: level may not be negative");

    GLsizei maxTextureSize = mContext->MaxTextureSizeForTarget(TexImageTargetToTexTarget(texImageTarget));
    if (!(maxTextureSize >> level))
        return mContext->ErrorInvalidValue("copyTexSubImage2D: 2^level exceeds maximum texture size");

    if (width < 0 || height < 0)
        return mContext->ErrorInvalidValue("copyTexSubImage2D: width and height may not be negative");

    if (xOffset < 0 || yOffset < 0)
        return mContext->ErrorInvalidValue("copyTexSubImage2D: xOffset and yOffset may not be negative");

    if (!HasImageInfoAt(texImageTarget, level))
        return mContext->ErrorInvalidOperation("copyTexSubImage2D: no texture image previously defined for this level and face");

    const WebGLTexture::ImageInfo& imageInfo = ImageInfoAt(texImageTarget, level);
    GLsizei texWidth = imageInfo.Width();
    GLsizei texHeight = imageInfo.Height();

    if (xOffset + width > texWidth || xOffset + width < 0)
      return mContext->ErrorInvalidValue("copyTexSubImage2D: xOffset+width is too large");

    if (yOffset + height > texHeight || yOffset + height < 0)
      return mContext->ErrorInvalidValue("copyTexSubImage2D: yOffset+height is too large");

    if (!mContext->mBoundReadFramebuffer)
        mContext->ClearBackbufferIfNeeded();

    if (imageInfo.HasUninitializedImageData()) {
        bool coversWholeImage = xOffset == 0 &&
                                yOffset == 0 &&
                                width == texWidth &&
                                height == texHeight;
        if (coversWholeImage) {
            SetImageDataStatus(texImageTarget, level, WebGLImageDataStatus::InitializedImageData);
        } else {
            if (!EnsureInitializedImageData(texImageTarget, level))
                return;
        }
    }

    TexInternalFormat internalFormat;
    TexType type;
    UnsizedInternalFormatAndTypeFromEffectiveInternalFormat(imageInfo.EffectiveInternalFormat(),
                                             &internalFormat, &type);
    return CopyTexSubImage2D_base(texImageTarget, level, internalFormat, xOffset, yOffset, x, y, width, height, true);
}


GLenum
WebGLTexture::CheckedTexImage2D(TexImageTarget texImageTarget,
                                       GLint level,
                                       TexInternalFormat internalFormat,
                                       GLsizei width,
                                       GLsizei height,
                                       GLint border,
                                       TexFormat unpackFormat,
                                       TexType unpackType,
                                       const GLvoid* data)
{
    TexInternalFormat effectiveInternalFormat =
        EffectiveInternalFormatFromInternalFormatAndType(internalFormat, unpackType);
    bool sizeMayChange = true;

    if (HasImageInfoAt(texImageTarget, level)) {
        const WebGLTexture::ImageInfo& imageInfo = ImageInfoAt(texImageTarget, level);
        sizeMayChange = width != imageInfo.Width() ||
                        height != imageInfo.Height() ||
                        effectiveInternalFormat != imageInfo.EffectiveInternalFormat();
    }

    gl::GLContext* gl = mContext->gl;

    // Convert to format and type required by OpenGL 'driver'.
    GLenum driverType = LOCAL_GL_NONE;
    GLenum driverInternalFormat = LOCAL_GL_NONE;
    GLenum driverFormat = LOCAL_GL_NONE;
    DriverFormatsFromEffectiveInternalFormat(gl,
                                             effectiveInternalFormat,
                                             &driverInternalFormat,
                                             &driverFormat,
                                             &driverType);

    if (sizeMayChange) {
        mContext->GetAndFlushUnderlyingGLErrors();
    }

    gl->fTexImage2D(texImageTarget.get(), level, driverInternalFormat, width, height, border, driverFormat, driverType, data);

    if (effectiveInternalFormat != driverInternalFormat)
        SetLegacyTextureSwizzle(gl, texImageTarget.get(), internalFormat.get());

    GLenum error = LOCAL_GL_NO_ERROR;
    if (sizeMayChange) {
        error = mContext->GetAndFlushUnderlyingGLErrors();
    }

    return error;
}

void
WebGLTexture::TexImage2D_base(TexImageTarget texImageTarget, GLint level,
                              GLenum internalFormat,
                              GLsizei width, GLsizei height, GLsizei srcStrideOrZero,
                              GLint border,
                              GLenum unpackFormat,
                              GLenum unpackType,
                              void* data, uint32_t byteLength,
                              js::Scalar::Type jsArrayType,
                              WebGLTexelFormat srcFormat, bool srcPremultiplied)
{
    const WebGLTexImageFunc func = WebGLTexImageFunc::TexImage;
    const WebGLTexDimensions dims = WebGLTexDimensions::Tex2D;

    if (unpackType == LOCAL_GL_HALF_FLOAT_OES) {
        unpackType = LOCAL_GL_HALF_FLOAT;
    }

    if (!mContext->ValidateTexImage(texImageTarget, level, internalFormat,
                          0, 0, 0,
                          width, height, 0,
                          border, unpackFormat, unpackType, func, dims))
    {
        return;
    }

    const bool isDepthTexture = unpackFormat == LOCAL_GL_DEPTH_COMPONENT ||
                                unpackFormat == LOCAL_GL_DEPTH_STENCIL;

    if (isDepthTexture && !mContext->IsWebGL2()) {
        if (data != nullptr || level != 0)
            return mContext->ErrorInvalidOperation("texImage2D: "
                                         "with format of DEPTH_COMPONENT or DEPTH_STENCIL, "
                                         "data must be nullptr, "
                                         "level must be zero");
    }

    if (!mContext->ValidateTexInputData(unpackType, jsArrayType, func, dims))
        return;

    TexInternalFormat effectiveInternalFormat =
        EffectiveInternalFormatFromInternalFormatAndType(internalFormat, unpackType);

    if (effectiveInternalFormat == LOCAL_GL_NONE) {
        return mContext->ErrorInvalidOperation("texImage2D: bad combination of internalFormat and type");
    }

    size_t srcTexelSize = size_t(-1);
    if (srcFormat == WebGLTexelFormat::Auto) {
        // we need to find the exact sized format of the source data. Slightly abusing
        // EffectiveInternalFormatFromInternalFormatAndType for that purpose. Really, an unsized source format
        // is the same thing as an unsized internalFormat.
        TexInternalFormat effectiveSourceFormat =
            EffectiveInternalFormatFromInternalFormatAndType(unpackFormat, unpackType);
        MOZ_ASSERT(effectiveSourceFormat != LOCAL_GL_NONE); // should have validated format/type combo earlier
        const size_t srcbitsPerTexel = GetBitsPerTexel(effectiveSourceFormat);
        MOZ_ASSERT((srcbitsPerTexel % 8) == 0); // should not have compressed formats here.
        srcTexelSize = srcbitsPerTexel / 8;
    } else {
        srcTexelSize = WebGLTexelConversions::TexelBytesForFormat(srcFormat);
    }

    CheckedUint32 checked_neededByteLength =
        mContext->GetImageSize(height, width, 1, srcTexelSize, mContext->mPixelStoreUnpackAlignment);

    CheckedUint32 checked_plainRowSize = CheckedUint32(width) * srcTexelSize;
    CheckedUint32 checked_alignedRowSize =
        RoundedToNextMultipleOf(checked_plainRowSize.value(), mContext->mPixelStoreUnpackAlignment);

    if (!checked_neededByteLength.isValid())
        return mContext->ErrorInvalidOperation("texImage2D: integer overflow computing the needed buffer size");

    uint32_t bytesNeeded = checked_neededByteLength.value();

    if (byteLength && byteLength < bytesNeeded)
        return mContext->ErrorInvalidOperation("texImage2D: not enough data for operation (need %d, have %d)",
                                 bytesNeeded, byteLength);

    if (mImmutable) {
        return mContext->ErrorInvalidOperation(
            "texImage2D: disallowed because the texture "
            "bound to this target has already been made immutable by texStorage2D");
    }
    mContext->MakeContextCurrent();

    nsAutoArrayPtr<uint8_t> convertedData;
    void* pixels = nullptr;
    WebGLImageDataStatus imageInfoStatusIfSuccess = WebGLImageDataStatus::UninitializedImageData;

    WebGLTexelFormat dstFormat = GetWebGLTexelFormat(effectiveInternalFormat);
    WebGLTexelFormat actualSrcFormat = srcFormat == WebGLTexelFormat::Auto ? dstFormat : srcFormat;

    if (byteLength) {
        size_t   bitsPerTexel = GetBitsPerTexel(effectiveInternalFormat);
        MOZ_ASSERT((bitsPerTexel % 8) == 0); // should not have compressed formats here.
        size_t   dstTexelSize = bitsPerTexel / 8;
        size_t   srcStride = srcStrideOrZero ? srcStrideOrZero : checked_alignedRowSize.value();
        size_t   dstPlainRowSize = dstTexelSize * width;
        size_t   unpackAlignment = mContext->mPixelStoreUnpackAlignment;
        size_t   dstStride = ((dstPlainRowSize + unpackAlignment-1) / unpackAlignment) * unpackAlignment;

        if (actualSrcFormat == dstFormat &&
            srcPremultiplied == mContext->mPixelStorePremultiplyAlpha &&
            srcStride == dstStride &&
            !mContext->mPixelStoreFlipY)
        {
            // no conversion, no flipping, so we avoid copying anything and just pass the source pointer
            pixels = data;
        }
        else
        {
            size_t convertedDataSize = height * dstStride;
            convertedData = new (fallible) uint8_t[convertedDataSize];
            if (!convertedData) {
                mContext->ErrorOutOfMemory("texImage2D: Ran out of memory when allocating"
                                 " a buffer for doing format conversion.");
                return;
            }
            if (!mContext->ConvertImage(width, height, srcStride, dstStride,
                              static_cast<uint8_t*>(data), convertedData,
                              actualSrcFormat, srcPremultiplied,
                              dstFormat, mContext->mPixelStorePremultiplyAlpha, dstTexelSize))
            {
                return mContext->ErrorInvalidOperation("texImage2D: Unsupported texture format conversion");
            }
            pixels = reinterpret_cast<void*>(convertedData.get());
        }
        imageInfoStatusIfSuccess = WebGLImageDataStatus::InitializedImageData;
    }

    GLenum error = CheckedTexImage2D(texImageTarget, level, internalFormat, width,
                                     height, border, unpackFormat, unpackType, pixels);

    if (error) {
        mContext->GenerateWarning("texImage2D generated error %s", mContext->ErrorName(error));
        return;
    }

    // in all of the code paths above, we should have either initialized data,
    // or allocated data and left it uninitialized, but in any case we shouldn't
    // have NoImageData at this point.
    MOZ_ASSERT(imageInfoStatusIfSuccess != WebGLImageDataStatus::NoImageData);

    SetImageInfo(texImageTarget, level, width, height, 1,
                      effectiveInternalFormat, imageInfoStatusIfSuccess);
}

void
WebGLTexture::TexImage2D(TexImageTarget texImageTarget, GLint level,
                         GLenum internalFormat, GLsizei width,
                         GLsizei height, GLint border, GLenum unpackFormat,
                         GLenum unpackType, const dom::Nullable<dom::ArrayBufferView>& maybeView,
                         ErrorResult* const out_rv)
{
    void* data;
    uint32_t length;
    js::Scalar::Type jsArrayType;
    if (maybeView.IsNull()) {
        data = nullptr;
        length = 0;
        jsArrayType = js::Scalar::MaxTypedArrayViewType;
    } else {
        const dom::ArrayBufferView& view = maybeView.Value();
        view.ComputeLengthAndData();

        data = view.Data();
        length = view.Length();
        jsArrayType = view.Type();
    }

    const char funcName[] = "texImage2D";
    if (!DoesTargetMatchDimensions(mContext, texImageTarget, 2, funcName))
        return;

    return TexImage2D_base(texImageTarget, level, internalFormat, width, height, 0, border, unpackFormat, unpackType,
                           data, length, jsArrayType,
                           WebGLTexelFormat::Auto, false);
}

void
WebGLTexture::TexImage2D(TexImageTarget texImageTarget, GLint level,
                         GLenum internalFormat, GLenum unpackFormat,
                         GLenum unpackType, dom::ImageData* imageData, ErrorResult* const out_rv)
{
    if (!imageData) {
        // Spec says to generate an INVALID_VALUE error
        return mContext->ErrorInvalidValue("texImage2D: null ImageData");
    }

    dom::Uint8ClampedArray arr;
    DebugOnly<bool> inited = arr.Init(imageData->GetDataObject());
    MOZ_ASSERT(inited);
    arr.ComputeLengthAndData();

    void* pixelData = arr.Data();
    const uint32_t pixelDataLength = arr.Length();

    const char funcName[] = "texImage2D";
    if (!DoesTargetMatchDimensions(mContext, texImageTarget, 2, funcName))
        return;

    return TexImage2D_base(texImageTarget, level, internalFormat, imageData->Width(),
                           imageData->Height(), 4*imageData->Width(), 0,
                           unpackFormat, unpackType, pixelData, pixelDataLength, js::Scalar::MaxTypedArrayViewType,
                           WebGLTexelFormat::RGBA8, false);
}

void
WebGLTexture::TexImage2D(TexImageTarget texImageTarget, GLint level,
                GLenum internalFormat, GLenum unpackFormat, GLenum unpackType,
                dom::Element* elem, ErrorResult* out_rv)
{
    const char funcName[] = "texImage2D";
    if (!DoesTargetMatchDimensions(mContext, texImageTarget, 2, funcName))
        return;

    if (level < 0)
        return mContext->ErrorInvalidValue("texImage2D: level is negative");

    const int32_t maxLevel = mContext->MaxTextureLevelForTexImageTarget(texImageTarget);
    if (level > maxLevel) {
        mContext->ErrorInvalidValue("texImage2D: level %d is too large, max is %d",
                          level, maxLevel);
        return;
    }

    // Trying to handle the video by GPU directly first
    if (TexImageFromVideoElement(texImageTarget, level, internalFormat,
                                 unpackFormat, unpackType, elem))
    {
        return;
    }

    RefPtr<gfx::DataSourceSurface> data;
    WebGLTexelFormat srcFormat;
    nsLayoutUtils::SurfaceFromElementResult res = mContext->SurfaceFromElement(elem);
    *out_rv = mContext->SurfaceFromElementResultToImageSurface(res, data, &srcFormat);
    if (out_rv->Failed() || !data)
        return;

    gfx::IntSize size = data->GetSize();
    uint32_t byteLength = data->Stride() * size.height;
    return TexImage2D_base(texImageTarget, level, internalFormat,
                           size.width, size.height, data->Stride(), 0,
                           unpackFormat, unpackType, data->GetData(), byteLength,
                           js::Scalar::MaxTypedArrayViewType, srcFormat,
                           res.mIsPremultiplied);
}


void
WebGLTexture::TexSubImage2D_base(TexImageTarget texImageTarget, GLint level,
                                 GLint xOffset, GLint yOffset,
                                 GLsizei width, GLsizei height, GLsizei srcStrideOrZero,
                                 GLenum unpackFormat, GLenum unpackType,
                                 void* data, uint32_t byteLength,
                                 js::Scalar::Type jsArrayType,
                                 WebGLTexelFormat srcFormat, bool srcPremultiplied)
{
    const WebGLTexImageFunc func = WebGLTexImageFunc::TexSubImage;
    const WebGLTexDimensions dims = WebGLTexDimensions::Tex2D;

    if (unpackType == LOCAL_GL_HALF_FLOAT_OES)
        unpackType = LOCAL_GL_HALF_FLOAT;

    const char funcName[] = "texSubImage2D";
    if (!DoesTargetMatchDimensions(mContext, texImageTarget, 2, funcName))
        return;

    if (!HasImageInfoAt(texImageTarget, level))
        return mContext->ErrorInvalidOperation("texSubImage2D: no previously defined texture image");

    const WebGLTexture::ImageInfo& imageInfo = ImageInfoAt(texImageTarget, level);
    const TexInternalFormat existingEffectiveInternalFormat = imageInfo.EffectiveInternalFormat();

    if (!mContext->ValidateTexImage(texImageTarget, level,
                          existingEffectiveInternalFormat.get(),
                          xOffset, yOffset, 0,
                          width, height, 0,
                          0, unpackFormat, unpackType, func, dims))
    {
        return;
    }

    if (!mContext->ValidateTexInputData(unpackType, jsArrayType, func, dims))
        return;

    if (unpackType != TypeFromInternalFormat(existingEffectiveInternalFormat)) {
        return mContext->ErrorInvalidOperation("texSubImage2D: type differs from that of the existing image");
    }

    size_t srcTexelSize = size_t(-1);
    if (srcFormat == WebGLTexelFormat::Auto) {
        const size_t bitsPerTexel = GetBitsPerTexel(existingEffectiveInternalFormat);
        MOZ_ASSERT((bitsPerTexel % 8) == 0); // should not have compressed formats here.
        srcTexelSize = bitsPerTexel / 8;
    } else {
        srcTexelSize = WebGLTexelConversions::TexelBytesForFormat(srcFormat);
    }

    if (width == 0 || height == 0)
        return; // ES 2.0 says it has no effect, we better return right now

    CheckedUint32 checked_neededByteLength =
        mContext->GetImageSize(height, width, 1, srcTexelSize, mContext->mPixelStoreUnpackAlignment);

    CheckedUint32 checked_plainRowSize = CheckedUint32(width) * srcTexelSize;

    CheckedUint32 checked_alignedRowSize =
        RoundedToNextMultipleOf(checked_plainRowSize.value(), mContext->mPixelStoreUnpackAlignment);

    if (!checked_neededByteLength.isValid())
        return mContext->ErrorInvalidOperation("texSubImage2D: integer overflow computing the needed buffer size");

    uint32_t bytesNeeded = checked_neededByteLength.value();

    if (byteLength < bytesNeeded)
        return mContext->ErrorInvalidOperation("texSubImage2D: not enough data for operation (need %d, have %d)", bytesNeeded, byteLength);

    if (imageInfo.HasUninitializedImageData()) {
        bool coversWholeImage = xOffset == 0 &&
                                yOffset == 0 &&
                                width == imageInfo.Width() &&
                                height == imageInfo.Height();
        if (coversWholeImage) {
            SetImageDataStatus(texImageTarget, level, WebGLImageDataStatus::InitializedImageData);
        } else {
            if (!EnsureInitializedImageData(texImageTarget, level))
                return;
        }
    }
    mContext->MakeContextCurrent();
    gl::GLContext* gl = mContext->gl;

    size_t   srcStride = srcStrideOrZero ? srcStrideOrZero : checked_alignedRowSize.value();
    uint32_t dstTexelSize = GetBitsPerTexel(existingEffectiveInternalFormat) / 8;
    size_t   dstPlainRowSize = dstTexelSize * width;
    // There are checks above to ensure that this won't overflow.
    size_t   dstStride = RoundedToNextMultipleOf(dstPlainRowSize, mContext->mPixelStoreUnpackAlignment).value();

    void* pixels = data;
    nsAutoArrayPtr<uint8_t> convertedData;

    WebGLTexelFormat dstFormat = GetWebGLTexelFormat(existingEffectiveInternalFormat);
    WebGLTexelFormat actualSrcFormat = srcFormat == WebGLTexelFormat::Auto ? dstFormat : srcFormat;

    // no conversion, no flipping, so we avoid copying anything and just pass the source pointer
    bool noConversion = (actualSrcFormat == dstFormat &&
                         srcPremultiplied == mContext->mPixelStorePremultiplyAlpha &&
                         srcStride == dstStride &&
                         !mContext->mPixelStoreFlipY);

    if (!noConversion) {
        size_t convertedDataSize = height * dstStride;
        convertedData = new (fallible) uint8_t[convertedDataSize];
        if (!convertedData) {
            mContext->ErrorOutOfMemory("texImage2D: Ran out of memory when allocating"
                             " a buffer for doing format conversion.");
            return;
        }
        if (!mContext->ConvertImage(width, height, srcStride, dstStride,
                          static_cast<const uint8_t*>(data), convertedData,
                          actualSrcFormat, srcPremultiplied,
                          dstFormat, mContext->mPixelStorePremultiplyAlpha, dstTexelSize))
        {
            return mContext->ErrorInvalidOperation("texSubImage2D: Unsupported texture format conversion");
        }
        pixels = reinterpret_cast<void*>(convertedData.get());
    }

    GLenum driverType = LOCAL_GL_NONE;
    GLenum driverInternalFormat = LOCAL_GL_NONE;
    GLenum driverFormat = LOCAL_GL_NONE;
    DriverFormatsFromEffectiveInternalFormat(gl,
                                             existingEffectiveInternalFormat,
                                             &driverInternalFormat,
                                             &driverFormat,
                                             &driverType);

    gl->fTexSubImage2D(texImageTarget.get(), level, xOffset, yOffset, width, height, driverFormat, driverType, pixels);
}

void
WebGLTexture::TexSubImage2D(TexImageTarget texImageTarget, GLint level,
                            GLint xOffset, GLint yOffset,
                            GLsizei width, GLsizei height,
                            GLenum unpackFormat, GLenum unpackType,
                            const dom::Nullable<dom::ArrayBufferView>& maybeView,
                            ErrorResult* const out_rv)
{
    if (maybeView.IsNull())
        return mContext->ErrorInvalidValue("texSubImage2D: pixels must not be null!");

    const dom::ArrayBufferView& view = maybeView.Value();
    view.ComputeLengthAndData();

    const char funcName[] = "texSubImage2D";
    if (!DoesTargetMatchDimensions(mContext, texImageTarget, 2, funcName))
        return;

    return TexSubImage2D_base(texImageTarget, level, xOffset, yOffset,
                              width, height, 0, unpackFormat, unpackType,
                              view.Data(), view.Length(), view.Type(),
                              WebGLTexelFormat::Auto, false);
}

void
WebGLTexture::TexSubImage2D(TexImageTarget texImageTarget, GLint level,
                            GLint xOffset, GLint yOffset,
                            GLenum unpackFormat, GLenum unpackType, dom::ImageData* imageData,
                            ErrorResult* const out_rv)
{
    if (!imageData)
        return mContext->ErrorInvalidValue("texSubImage2D: pixels must not be null!");

    dom::Uint8ClampedArray arr;
    DebugOnly<bool> inited = arr.Init(imageData->GetDataObject());
    MOZ_ASSERT(inited);
    arr.ComputeLengthAndData();

    return TexSubImage2D_base(texImageTarget, level, xOffset, yOffset,
                              imageData->Width(), imageData->Height(),
                              4*imageData->Width(), unpackFormat, unpackType,
                              arr.Data(), arr.Length(),
                              js::Scalar::MaxTypedArrayViewType,
                              WebGLTexelFormat::RGBA8, false);
}



bool
WebGLTexture::TexImageFromVideoElement(TexImageTarget texImageTarget,
                                       GLint level, GLenum internalFormat,
                                       GLenum unpackFormat, GLenum unpackType,
                                       mozilla::dom::Element* elem)
{
    if (unpackType == LOCAL_GL_HALF_FLOAT_OES &&
        !mContext->gl->IsExtensionSupported(gl::GLContext::OES_texture_half_float))
    {
        unpackType = LOCAL_GL_HALF_FLOAT;
    }

    if (!mContext->ValidateTexImageFormatAndType(unpackFormat, unpackType,
                                       WebGLTexImageFunc::TexImage,
                                       WebGLTexDimensions::Tex2D))
    {
        return false;
    }

    dom::HTMLVideoElement* video = dom::HTMLVideoElement::FromContentOrNull(elem);
    if (!video)
        return false;

    uint16_t readyState;
    if (NS_SUCCEEDED(video->GetReadyState(&readyState)) &&
        readyState < nsIDOMHTMLMediaElement::HAVE_CURRENT_DATA)
    {
        //No frame inside, just return
        return false;
    }

    // If it doesn't have a principal, just bail
    nsCOMPtr<nsIPrincipal> principal = video->GetCurrentPrincipal();
    if (!principal)
        return false;

    mozilla::layers::ImageContainer* container = video->GetImageContainer();
    if (!container)
        return false;

    if (video->GetCORSMode() == CORS_NONE) {
        bool subsumes;
        nsresult rv = mContext->mCanvasElement->NodePrincipal()->Subsumes(principal, &subsumes);
        if (NS_FAILED(rv) || !subsumes) {
            mContext->GenerateWarning("It is forbidden to load a WebGL texture from a cross-domain element that has not been validated with CORS. "
                                "See https://developer.mozilla.org/en/WebGL/Cross-Domain_Textures");
            return false;
        }
    }

    layers::AutoLockImage lockedImage(container);
    layers::Image* srcImage = lockedImage.GetImage();
    if (!srcImage) {
      return false;
    }

    gl::GLContext* gl = mContext->gl;
    gl->MakeCurrent();

    const WebGLTexture::ImageInfo& info = ImageInfoAt(texImageTarget, 0);
    bool dimensionsMatch = info.Width() == srcImage->GetSize().width &&
                           info.Height() == srcImage->GetSize().height;
    if (!dimensionsMatch) {
        // we need to allocation
        gl->fTexImage2D(texImageTarget.get(), level, internalFormat,
                        srcImage->GetSize().width, srcImage->GetSize().height,
                        0, unpackFormat, unpackType, nullptr);
    }

    const gl::OriginPos destOrigin = mContext->mPixelStoreFlipY ? gl::OriginPos::BottomLeft
                                                      : gl::OriginPos::TopLeft;
    bool ok = gl->BlitHelper()->BlitImageToTexture(srcImage,
                                                   srcImage->GetSize(),
                                                   mGLName,
                                                   texImageTarget.get(),
                                                   destOrigin);
    if (ok) {
        TexInternalFormat effectiveInternalFormat =
            EffectiveInternalFormatFromInternalFormatAndType(internalFormat,
                                                             unpackType);
        MOZ_ASSERT(effectiveInternalFormat != LOCAL_GL_NONE);
        SetImageInfo(texImageTarget, level, srcImage->GetSize().width,
                          srcImage->GetSize().height, 1,
                          effectiveInternalFormat,
                          WebGLImageDataStatus::InitializedImageData);
        Bind(TexImageTargetToTexTarget(texImageTarget));
    }
    return ok;
}

void
WebGLTexture::TexSubImage2D(TexImageTarget texImageTarget, GLint level, GLint xOffset,
                            GLint yOffset, GLenum unpackFormat, GLenum unpackType,
                            dom::Element* elem, ErrorResult* const out_rv)
{
    const char funcName[] = "texSubImage2D";
    if (!DoesTargetMatchDimensions(mContext, texImageTarget, 2, funcName))
        return;

    if (level < 0)
        return mContext->ErrorInvalidValue("texSubImage2D: level is negative");

    const int32_t maxLevel = mContext->MaxTextureLevelForTexImageTarget(texImageTarget);
    if (level > maxLevel) {
        mContext->ErrorInvalidValue("texSubImage2D: level %d is too large, max is %d",
                          level, maxLevel);
        return;
    }

    const WebGLTexture::ImageInfo& imageInfo = ImageInfoAt(texImageTarget, level);
    const TexInternalFormat internalFormat = imageInfo.EffectiveInternalFormat();

    // Trying to handle the video by GPU directly first
    if (TexImageFromVideoElement(texImageTarget, level, internalFormat.get(), unpackFormat, unpackType, elem))
    {
        return;
    }

    RefPtr<gfx::DataSourceSurface> data;
    WebGLTexelFormat srcFormat;
    nsLayoutUtils::SurfaceFromElementResult res = mContext->SurfaceFromElement(elem);
    *out_rv = mContext->SurfaceFromElementResultToImageSurface(res, data, &srcFormat);
    if (out_rv->Failed() || !data)
        return;

    gfx::IntSize size = data->GetSize();
    uint32_t byteLength = data->Stride() * size.height;
    TexSubImage2D_base(texImageTarget, level, xOffset, yOffset, size.width,
                       size.height, data->Stride(), unpackFormat, unpackType, data->GetData(),
                       byteLength, js::Scalar::MaxTypedArrayViewType, srcFormat,
                       res.mIsPremultiplied);
}

bool
WebGLTexture::ValidateSizedInternalFormat(GLenum internalFormat, const char* info)
{
    switch (internalFormat) {
        // Sized Internal Formats
        // https://www.khronos.org/opengles/sdk/docs/man3/html/glTexStorage2D.xhtml
    case LOCAL_GL_R8:
    case LOCAL_GL_R8_SNORM:
    case LOCAL_GL_R16F:
    case LOCAL_GL_R32F:
    case LOCAL_GL_R8UI:
    case LOCAL_GL_R8I:
    case LOCAL_GL_R16UI:
    case LOCAL_GL_R16I:
    case LOCAL_GL_R32UI:
    case LOCAL_GL_R32I:
    case LOCAL_GL_RG8:
    case LOCAL_GL_RG8_SNORM:
    case LOCAL_GL_RG16F:
    case LOCAL_GL_RG32F:
    case LOCAL_GL_RG8UI:
    case LOCAL_GL_RG8I:
    case LOCAL_GL_RG16UI:
    case LOCAL_GL_RG16I:
    case LOCAL_GL_RG32UI:
    case LOCAL_GL_RG32I:
    case LOCAL_GL_RGB8:
    case LOCAL_GL_SRGB8:
    case LOCAL_GL_RGB565:
    case LOCAL_GL_RGB8_SNORM:
    case LOCAL_GL_R11F_G11F_B10F:
    case LOCAL_GL_RGB9_E5:
    case LOCAL_GL_RGB16F:
    case LOCAL_GL_RGB32F:
    case LOCAL_GL_RGB8UI:
    case LOCAL_GL_RGB8I:
    case LOCAL_GL_RGB16UI:
    case LOCAL_GL_RGB16I:
    case LOCAL_GL_RGB32UI:
    case LOCAL_GL_RGB32I:
    case LOCAL_GL_RGBA8:
    case LOCAL_GL_SRGB8_ALPHA8:
    case LOCAL_GL_RGBA8_SNORM:
    case LOCAL_GL_RGB5_A1:
    case LOCAL_GL_RGBA4:
    case LOCAL_GL_RGB10_A2:
    case LOCAL_GL_RGBA16F:
    case LOCAL_GL_RGBA32F:
    case LOCAL_GL_RGBA8UI:
    case LOCAL_GL_RGBA8I:
    case LOCAL_GL_RGB10_A2UI:
    case LOCAL_GL_RGBA16UI:
    case LOCAL_GL_RGBA16I:
    case LOCAL_GL_RGBA32I:
    case LOCAL_GL_RGBA32UI:
    case LOCAL_GL_DEPTH_COMPONENT16:
    case LOCAL_GL_DEPTH_COMPONENT24:
    case LOCAL_GL_DEPTH_COMPONENT32F:
    case LOCAL_GL_DEPTH24_STENCIL8:
    case LOCAL_GL_DEPTH32F_STENCIL8:
        return true;
    }

    if (IsCompressedTextureFormat(internalFormat))
        return true;

    nsCString name;
    mContext->EnumName(internalFormat, &name);
    mContext->ErrorInvalidEnum("%s: invalid internal format %s", info, name.get());

    return false;
}


/** Validates parameters to texStorage{2D,3D} */
bool
WebGLTexture::ValidateTexStorage(TexTarget texTarget, GLsizei levels, GLenum internalFormat,
                                      GLsizei width, GLsizei height, GLsizei depth,
                                      const char* info)
{
    // GL_INVALID_OPERATION is generated if the texture object currently bound to target already has
    // GL_TEXTURE_IMMUTABLE_FORMAT set to GL_TRUE.
    if (mImmutable) {
        mContext->ErrorInvalidOperation("%s: texture bound to target %s is already immutable",
                                        info, mContext->EnumName(texTarget.get()));
        return false;
    }

    // GL_INVALID_ENUM is generated if internalFormat is not a valid sized internal format.
    if (!ValidateSizedInternalFormat(internalFormat, info))
        return false;

    // GL_INVALID_VALUE is generated if width, height or levels are less than 1.
    if (width < 1)  { mContext->ErrorInvalidValue("%s: width is < 1", info);  return false; }
    if (height < 1) { mContext->ErrorInvalidValue("%s: height is < 1", info); return false; }
    if (depth < 1)  { mContext->ErrorInvalidValue("%s: depth is < 1", info);  return false; }
    if (levels < 1) { mContext->ErrorInvalidValue("%s: levels is < 1", info); return false; }

    // GL_INVALID_OPERATION is generated if levels is greater than floor(log2(max(width, height, depth)))+1.
    if (FloorLog2(std::max(std::max(width, height), depth)) + 1 < levels) {
        mContext->ErrorInvalidOperation("%s: too many levels for given texture dimensions", info);
        return false;
    }

    return true;
}

void
WebGLTexture::TexStorage2D(TexTarget texTarget, GLsizei levels, GLenum internalFormat, GLsizei width, GLsizei height)
{
    // GL_INVALID_ENUM is generated if target is not one of the accepted target enumerants.
    if (texTarget != LOCAL_GL_TEXTURE_2D && texTarget != LOCAL_GL_TEXTURE_CUBE_MAP)
        return mContext->ErrorInvalidEnum("texStorage2D: target is not TEXTURE_2D or TEXTURE_CUBE_MAP");

    if (!ValidateTexStorage(texTarget, levels, internalFormat, width, height, 1, "texStorage2D"))
        return;

    gl::GLContext* gl = mContext->gl;
    gl->MakeCurrent();

    mContext->GetAndFlushUnderlyingGLErrors();
    gl->fTexStorage2D(texTarget.get(), levels, internalFormat, width, height);
    GLenum error = mContext->GetAndFlushUnderlyingGLErrors();
    if (error) {
        return mContext->GenerateWarning("texStorage2D generated error %s", mContext->ErrorName(error));
    }

    SetImmutable();

    const size_t facesCount = (texTarget == LOCAL_GL_TEXTURE_2D) ? 1 : 6;
    GLsizei w = width;
    GLsizei h = height;
    for (size_t l = 0; l < size_t(levels); l++) {
        for (size_t f = 0; f < facesCount; f++) {
            SetImageInfo(TexImageTargetForTargetAndFace(texTarget, f),
                              l, w, h, 1,
                              internalFormat,
                              WebGLImageDataStatus::UninitializedImageData);
        }
        w = std::max(1, w / 2);
        h = std::max(1, h / 2);
    }
}

void
WebGLTexture::TexStorage3D(TexTarget texTarget, GLsizei levels, GLenum internalFormat,
                            GLsizei width, GLsizei height, GLsizei depth)
{
    // GL_INVALID_ENUM is generated if target is not one of the accepted target enumerants.
    if (texTarget != LOCAL_GL_TEXTURE_3D)
        return mContext->ErrorInvalidEnum("texStorage3D: target is not TEXTURE_3D");

    if (!ValidateTexStorage(texTarget, levels, internalFormat, width, height, depth, "texStorage3D"))
        return;

    gl::GLContext* gl = mContext->gl;
    gl->MakeCurrent();

    mContext->GetAndFlushUnderlyingGLErrors();
    gl->fTexStorage3D(texTarget.get(), levels, internalFormat, width, height, depth);
    GLenum error = mContext->GetAndFlushUnderlyingGLErrors();
    if (error) {
        return mContext->GenerateWarning("texStorage3D generated error %s", mContext->ErrorName(error));
    }

    SetImmutable();

    GLsizei w = width;
    GLsizei h = height;
    GLsizei d = depth;
    for (size_t l = 0; l < size_t(levels); l++) {
        SetImageInfo(TexImageTargetForTargetAndFace(texTarget, 0),
                          l, w, h, d,
                          internalFormat,
                          WebGLImageDataStatus::UninitializedImageData);
        w = std::max(1, w >> 1);
        h = std::max(1, h >> 1);
        d = std::max(1, d >> 1);
    }
}

void
WebGLTexture::TexImage3D(TexImageTarget texImageTarget, GLint level, GLenum internalFormat,
                          GLsizei width, GLsizei height, GLsizei depth,
                          GLint border, GLenum unpackFormat, GLenum unpackType,
                          const dom::Nullable<dom::ArrayBufferView>& maybeView,
                          ErrorResult* const out_rv)
{
    void* data;
    size_t dataLength;
    js::Scalar::Type jsArrayType;
    if (maybeView.IsNull()) {
        data = nullptr;
        dataLength = 0;
        jsArrayType = js::Scalar::MaxTypedArrayViewType;
    } else {
        const dom::ArrayBufferView& view = maybeView.Value();
        view.ComputeLengthAndData();

        data = view.Data();
        dataLength = view.Length();
        jsArrayType = view.Type();
    }

    const char funcName[] = "texImage3D";
    if (!DoesTargetMatchDimensions(mContext, texImageTarget, 3, funcName))
        return;

    const WebGLTexImageFunc func = WebGLTexImageFunc::TexImage;
    const WebGLTexDimensions dims = WebGLTexDimensions::Tex3D;

    if (!mContext->ValidateTexImage(texImageTarget, level, internalFormat,
                          0, 0, 0,
                          width, height, depth,
                          border, unpackFormat, unpackType, func, dims))
    {
        return;
    }

    if (!mContext->ValidateTexInputData(unpackType, jsArrayType, func, dims))
        return;

    TexInternalFormat effectiveInternalFormat =
        EffectiveInternalFormatFromInternalFormatAndType(internalFormat, unpackType);

    if (effectiveInternalFormat == LOCAL_GL_NONE) {
        return mContext->ErrorInvalidOperation("texImage3D: bad combination of internalFormat and unpackType");
    }

    // we need to find the exact sized format of the source data. Slightly abusing
    // EffectiveInternalFormatFromInternalFormatAndType for that purpose. Really, an unsized source format
    // is the same thing as an unsized internalFormat.
    TexInternalFormat effectiveSourceFormat =
        EffectiveInternalFormatFromInternalFormatAndType(unpackFormat, unpackType);
    MOZ_ASSERT(effectiveSourceFormat != LOCAL_GL_NONE); // should have validated unpack format/type combo earlier
    const size_t srcbitsPerTexel = GetBitsPerTexel(effectiveSourceFormat);
    MOZ_ASSERT((srcbitsPerTexel % 8) == 0); // should not have compressed formats here.
    size_t srcTexelSize = srcbitsPerTexel / 8;

    CheckedUint32 checked_neededByteLength =
        mContext->GetImageSize(height, width, depth, srcTexelSize, mContext->mPixelStoreUnpackAlignment);

    if (!checked_neededByteLength.isValid())
        return mContext->ErrorInvalidOperation("texSubImage2D: integer overflow computing the needed buffer size");

    uint32_t bytesNeeded = checked_neededByteLength.value();

    if (dataLength && dataLength < bytesNeeded)
        return mContext->ErrorInvalidOperation("texImage3D: not enough data for operation (need %d, have %d)",
                                 bytesNeeded, dataLength);

    if (IsImmutable()) {
        return mContext->ErrorInvalidOperation(
            "texImage3D: disallowed because the texture "
            "bound to this target has already been made immutable by texStorage3D");
    }

    gl::GLContext* gl = mContext->gl;
    gl->MakeCurrent();

    GLenum driverUnpackType = LOCAL_GL_NONE;
    GLenum driverInternalFormat = LOCAL_GL_NONE;
    GLenum driverUnpackFormat = LOCAL_GL_NONE;
    DriverFormatsFromEffectiveInternalFormat(gl,
                                             effectiveInternalFormat,
                                             &driverInternalFormat,
                                             &driverUnpackFormat,
                                             &driverUnpackType);

    mContext->GetAndFlushUnderlyingGLErrors();
    gl->fTexImage3D(texImageTarget.get(), level,
                    driverInternalFormat,
                    width, height, depth,
                    0, driverUnpackFormat, driverUnpackType,
                    data);
    GLenum error = mContext->GetAndFlushUnderlyingGLErrors();
    if (error) {
        return mContext->GenerateWarning("texImage3D generated error %s", mContext->ErrorName(error));
    }

    SetImageInfo(texImageTarget, level,
                      width, height, depth,
                      effectiveInternalFormat,
                      data ? WebGLImageDataStatus::InitializedImageData
                           : WebGLImageDataStatus::UninitializedImageData);
}

void
WebGLTexture::TexSubImage3D(TexImageTarget texImageTarget, GLint level,
                             GLint xOffset, GLint yOffset, GLint zOffset,
                             GLsizei width, GLsizei height, GLsizei depth,
                             GLenum unpackFormat, GLenum unpackType,
                             const dom::Nullable<dom::ArrayBufferView>& maybeView,
                             ErrorResult* const out_rv)
{
    if (maybeView.IsNull())
        return mContext->ErrorInvalidValue("texSubImage3D: pixels must not be null!");

    const dom::ArrayBufferView& view = maybeView.Value();
    view.ComputeLengthAndData();

    const char funcName[] = "texSubImage3D";
    if (!DoesTargetMatchDimensions(mContext, texImageTarget, 3, funcName))
        return;

    const WebGLTexImageFunc func = WebGLTexImageFunc::TexSubImage;
    const WebGLTexDimensions dims = WebGLTexDimensions::Tex3D;

    if (!HasImageInfoAt(texImageTarget, level)) {
        return mContext->ErrorInvalidOperation("texSubImage3D: no previously defined texture image");
    }

    const WebGLTexture::ImageInfo& imageInfo = ImageInfoAt(texImageTarget, level);
    const TexInternalFormat existingEffectiveInternalFormat = imageInfo.EffectiveInternalFormat();
    TexInternalFormat existingUnsizedInternalFormat = LOCAL_GL_NONE;
    TexType existingType = LOCAL_GL_NONE;
    UnsizedInternalFormatAndTypeFromEffectiveInternalFormat(existingEffectiveInternalFormat,
                                                            &existingUnsizedInternalFormat,
                                                            &existingType);

    if (!mContext->ValidateTexImage(texImageTarget, level, existingEffectiveInternalFormat.get(),
                          xOffset, yOffset, zOffset,
                          width, height, depth,
                          0, unpackFormat, unpackType, func, dims))
    {
        return;
    }

    if (unpackType != existingType) {
        return mContext->ErrorInvalidOperation("texSubImage3D: type differs from that of the existing image");
    }

    js::Scalar::Type jsArrayType = view.Type();
    void* data = view.Data();
    size_t dataLength = view.Length();

    if (!mContext->ValidateTexInputData(unpackType, jsArrayType, func, dims))
        return;

    const size_t bitsPerTexel = GetBitsPerTexel(existingEffectiveInternalFormat);
    MOZ_ASSERT((bitsPerTexel % 8) == 0); // should not have compressed formats here.
    size_t srcTexelSize = bitsPerTexel / 8;

    if (width == 0 || height == 0 || depth == 0)
        return; // no effect, we better return right now

    CheckedUint32 checked_neededByteLength =
        mContext->GetImageSize(height, width, depth, srcTexelSize, mContext->mPixelStoreUnpackAlignment);

    if (!checked_neededByteLength.isValid())
        return mContext->ErrorInvalidOperation("texSubImage2D: integer overflow computing the needed buffer size");

    uint32_t bytesNeeded = checked_neededByteLength.value();

    if (dataLength < bytesNeeded)
        return mContext->ErrorInvalidOperation("texSubImage2D: not enough data for operation (need %d, have %d)", bytesNeeded, dataLength);

    if (imageInfo.HasUninitializedImageData()) {
        bool coversWholeImage = xOffset == 0 &&
                                yOffset == 0 &&
                                zOffset == 0 &&
                                width == imageInfo.Width() &&
                                height == imageInfo.Height() &&
                                depth == imageInfo.Depth();
        if (coversWholeImage) {
            SetImageDataStatus(texImageTarget, level, WebGLImageDataStatus::InitializedImageData);
        } else {
            if (!EnsureInitializedImageData(texImageTarget, level))
                return;
        }
    }

    GLenum driverUnpackType = LOCAL_GL_NONE;
    GLenum driverInternalFormat = LOCAL_GL_NONE;
    GLenum driverUnpackFormat = LOCAL_GL_NONE;
    DriverFormatsFromEffectiveInternalFormat(mContext->gl,
                                             existingEffectiveInternalFormat,
                                             &driverInternalFormat,
                                             &driverUnpackFormat,
                                             &driverUnpackType);

    mContext->MakeContextCurrent();
    mContext->gl->fTexSubImage3D(texImageTarget.get(), level,
                       xOffset, yOffset, zOffset,
                       width, height, depth,
                       driverUnpackFormat, driverUnpackType, data);

}


} // namespace mozilla
