void
WebGLContext::GenerateMipmap(GLenum rawTexTarget)
{
    TexTarget texTarget;
    WebGLTexture* tex;
    if (!ValidateTexTarget(this, rawTexTarget, "texParameter", &texTarget, &tex))
        return;

    tex->GenerateMipmap(texTarget);
}
void
WebGLContext::AssertCachedBindings()
{
#ifdef DEBUG
    MakeContextCurrent();

    GetAndFlushUnderlyingGLErrors();

    if (IsExtensionEnabled(WebGLExtensionID::OES_vertex_array_object)) {
        GLuint bound = mBoundVertexArray ? mBoundVertexArray->GLName() : 0;
        AssertUintParamCorrect(gl, LOCAL_GL_VERTEX_ARRAY_BINDING, bound);
    }

    // Bound object state
    if (IsWebGL2()) {
        GLuint bound = mBoundDrawFramebuffer ? mBoundDrawFramebuffer->GLName()
                                             : 0;
        AssertUintParamCorrect(gl, LOCAL_GL_DRAW_FRAMEBUFFER_BINDING, bound);

        bound = mBoundReadFramebuffer ? mBoundReadFramebuffer->GLName() : 0;
        AssertUintParamCorrect(gl, LOCAL_GL_READ_FRAMEBUFFER_BINDING, bound);
    } else {
        MOZ_ASSERT(mBoundDrawFramebuffer == mBoundReadFramebuffer);
        GLuint bound = mBoundDrawFramebuffer ? mBoundDrawFramebuffer->GLName()
                                             : 0;
        AssertUintParamCorrect(gl, LOCAL_GL_FRAMEBUFFER_BINDING, bound);
    }

    GLuint bound = mCurrentProgram ? mCurrentProgram->GLName() : 0;
    AssertUintParamCorrect(gl, LOCAL_GL_CURRENT_PROGRAM, bound);

    // Textures
    GLenum activeTexture = mActiveTexture + LOCAL_GL_TEXTURE0;
    AssertUintParamCorrect(gl, LOCAL_GL_ACTIVE_TEXTURE, activeTexture);

    WebGLTexture* curTex = ActiveBoundTextureForTarget(LOCAL_GL_TEXTURE_2D);
    bound = curTex ? curTex->GLName() : 0;
    AssertUintParamCorrect(gl, LOCAL_GL_TEXTURE_BINDING_2D, bound);

    curTex = ActiveBoundTextureForTarget(LOCAL_GL_TEXTURE_CUBE_MAP);
    bound = curTex ? curTex->GLName() : 0;
    AssertUintParamCorrect(gl, LOCAL_GL_TEXTURE_BINDING_CUBE_MAP, bound);

    // Buffers
    bound = mBoundArrayBuffer ? mBoundArrayBuffer->GLName() : 0;
    AssertUintParamCorrect(gl, LOCAL_GL_ARRAY_BUFFER_BINDING, bound);

    MOZ_ASSERT(mBoundVertexArray);
    WebGLBuffer* curBuff = mBoundVertexArray->mElementArrayBuffer;
    bound = curBuff ? curBuff->GLName() : 0;
    AssertUintParamCorrect(gl, LOCAL_GL_ELEMENT_ARRAY_BUFFER_BINDING, bound);

    MOZ_ASSERT(!GetAndFlushUnderlyingGLErrors());
#endif
}
void
WebGLContext::GenerateMipmap(GLenum rawTexTarget)
{
    const FuncScope funcScope(*this, "generateMipmap");
    const uint8_t funcDims = 0;

    TexTarget texTarget;
    WebGLTexture* tex;
    if (!ValidateTexTarget(this, funcDims, rawTexTarget, &texTarget, &tex))
        return;

    tex->GenerateMipmap(texTarget);
}
void
WebGLContext::CopyTexSubImage(uint8_t funcDims, GLenum rawTarget,
                              GLint level, GLint xOffset, GLint yOffset, GLint zOffset,
                              GLint x, GLint y, GLsizei width, GLsizei height)
{
    TexImageTarget target;
    WebGLTexture* tex;
    if (!ValidateTexImageTarget(this, funcDims, rawTarget, &target, &tex))
        return;

    tex->CopyTexSubImage(target, level, xOffset, yOffset, zOffset, x, y, width,
                         height);
}
void
WebGLContext::TexParameter_base(GLenum rawTexTarget, GLenum pname, GLint* maybeIntParam,
                                GLfloat* maybeFloatParam)
{
    MOZ_ASSERT(maybeIntParam || maybeFloatParam);

    TexTarget texTarget;
    WebGLTexture* tex;
    if (!ValidateTexTarget(this, rawTexTarget, "texParameter", &texTarget, &tex))
        return;

    tex->TexParameter(texTarget, pname, maybeIntParam, maybeFloatParam);
}
void
WebGLContext::TexParameter_base(GLenum rawTexTarget, GLenum pname,
                                const FloatOrInt& param)
{
    const FuncScope funcScope(*this, "texParameter");
    const uint8_t funcDims = 0;

    TexTarget texTarget;
    WebGLTexture* tex;
    if (!ValidateTexTarget(this, funcDims, rawTexTarget, &texTarget, &tex))
        return;

    tex->TexParameter(texTarget, pname, param);
}
void
WebGLContext::CompressedTexImage(uint8_t funcDims, GLenum rawTarget,
                                 GLint level, GLenum internalFormat, GLsizei width,
                                 GLsizei height, GLsizei depth, GLint border,
                                 const TexImageSource& src, const Maybe<GLsizei>& expectedImageSize)
{
    TexImageTarget target;
    WebGLTexture* tex;
    if (!ValidateTexImageTarget(this, funcDims, rawTarget, &target, &tex))
        return;

    tex->CompressedTexImage(target, level, internalFormat, width, height, depth,
                            border, src, expectedImageSize);
}
JS::Value
WebGLContext::GetTexParameter(GLenum rawTexTarget, GLenum pname)
{
    TexTarget texTarget;
    WebGLTexture* tex;
    if (!ValidateTexTarget(this, rawTexTarget, "texParameter", &texTarget, &tex))
        return JS::NullValue();

    if (!IsTexParamValid(pname)) {
        ErrorInvalidEnumInfo("getTexParameter: pname", pname);
        return JS::NullValue();
    }

    return tex->GetTexParameter(texTarget, pname);
}
void
WebGLContext::CopyTexSubImage2D(GLenum rawTexImageTarget, GLint level, GLint xOffset,
                                GLint yOffset, GLint x, GLint y, GLsizei width,
                                GLsizei height)
{
    TexImageTarget texImageTarget;
    WebGLTexture* tex;
    if (!ValidateTexImageTarget(this, rawTexImageTarget, "copyTexSubImage2D",
                                &texImageTarget, &tex))
    {
        return;
    }

    tex->CopyTexSubImage2D(texImageTarget, level, xOffset, yOffset, x, y, width, height);
}
void
WebGLContext::TexImage(uint8_t funcDims, GLenum rawTarget,
                       GLint level, GLenum internalFormat, GLsizei width, GLsizei height,
                       GLsizei depth, GLint border, GLenum unpackFormat,
                       GLenum unpackType, const TexImageSource& src)
{
    TexImageTarget target;
    WebGLTexture* tex;
    if (!ValidateTexImageTarget(this, funcDims, rawTarget, &target, &tex))
        return;

    const webgl::PackingInfo pi = {unpackFormat, unpackType};
    tex->TexImage(target, level, internalFormat, width, height, depth, border,
                  pi, src);
}
void
WebGLContext::CopyTexImage2D(GLenum rawTarget, GLint level, GLenum internalFormat,
                             GLint x, GLint y, GLsizei width, GLsizei height,
                             GLint border)
{
    const FuncScope funcScope(*this, "copyTexImage2D");
    const uint8_t funcDims = 2;

    TexImageTarget target;
    WebGLTexture* tex;
    if (!ValidateTexImageTarget(this, funcDims, rawTarget, &target, &tex))
        return;

    tex->CopyTexImage2D(target, level, internalFormat, x, y, width, height, border);
}
void
WebGLContext::CompressedTexSubImage(uint8_t funcDims,
                                    GLenum rawTarget, GLint level, GLint xOffset,
                                    GLint yOffset, GLint zOffset, GLsizei width,
                                    GLsizei height, GLsizei depth, GLenum unpackFormat,
                                    const TexImageSource& src, const Maybe<GLsizei>& expectedImageSize)
{
    TexImageTarget target;
    WebGLTexture* tex;
    if (!ValidateTexImageTarget(this, funcDims, rawTarget, &target, &tex))
        return;

    tex->CompressedTexSubImage(target, level, xOffset, yOffset, zOffset, width,
                               height, depth, unpackFormat, src, expectedImageSize);
}
void
WebGLContext::TexImage2D(GLenum rawTexImageTarget, GLint level, GLenum internalFormat,
                         GLenum unpackFormat, GLenum unpackType, dom::Element* elem,
                         ErrorResult* const out_rv)
{
    TexImageTarget texImageTarget;
    WebGLTexture* tex;
    if (!ValidateTexImageTarget(this, rawTexImageTarget, "texImage2D", &texImageTarget,
                                &tex))
    {
        return;
    }

    tex->TexImage2D(texImageTarget, level, internalFormat, unpackFormat, unpackType, elem,
                    out_rv);
}
void
WebGLContext::TexSubImage2D(GLenum rawTexImageTarget, GLint level, GLint xOffset, GLint yOffset,
                            GLenum unpackFormat, GLenum unpackType, dom::ImageData* imageData,
                            ErrorResult& out_rv)
{
    TexImageTarget texImageTarget;
    WebGLTexture* tex;
    if (!ValidateTexImageTarget(this, rawTexImageTarget, "texSubImage2D", &texImageTarget,
                                &tex))
    {
        return;
    }

    tex->TexSubImage2D(texImageTarget, level, xOffset, yOffset, unpackFormat, unpackType,
                       imageData, &out_rv);
}
void
WebGLContext::CompressedTexImage2D(GLenum rawTexImageTarget, GLint level,
                                   GLenum internalFormat, GLsizei width, GLsizei height,
                                   GLint border, const dom::ArrayBufferView& view)
{
    TexImageTarget texImageTarget;
    WebGLTexture* tex;
    if (!ValidateTexImageTarget(this, rawTexImageTarget, "compressedTexImage2D",
                                &texImageTarget, &tex))
    {
        return;
    }

    tex->CompressedTexImage2D(texImageTarget, level, internalFormat, width, height,
                              border, view);
}
void
WebGLContext::CopyTexImage2D(GLenum rawTexImageTarget, GLint level, GLenum internalFormat,
                             GLint x, GLint y, GLsizei width, GLsizei height,
                             GLint border)
{
    TexImageTarget texImageTarget;
    WebGLTexture* tex;
    if (!ValidateTexImageTarget(this, rawTexImageTarget, "copyTexImage2D",
                                &texImageTarget, &tex))
    {
        return;
    }

    tex->CopyTexImage2D(texImageTarget, level, internalFormat, x, y, width, height,
                        border);
}
void
WebGLContext::CompressedTexSubImage2D(GLenum rawTexImageTarget, GLint level,
                                      GLint xOffset, GLint yOffset, GLsizei width,
                                      GLsizei height, GLenum unpackFormat,
                                      const dom::ArrayBufferView& view)
{
    TexImageTarget texImageTarget;
    WebGLTexture* tex;
    if (!ValidateTexImageTarget(this, rawTexImageTarget, "compressedTexSubImage2D",
                                &texImageTarget, &tex))
    {
        return;
    }

    tex->CompressedTexSubImage2D(texImageTarget, level, xOffset, yOffset, width, height,
                                 unpackFormat, view);
}
JS::Value
WebGLContext::GetTexParameter(GLenum rawTexTarget, GLenum pname)
{
    const FuncScope funcScope(*this, "getTexParameter");
    const uint8_t funcDims = 0;

    TexTarget texTarget;
    WebGLTexture* tex;
    if (!ValidateTexTarget(this, funcDims, rawTexTarget, &texTarget, &tex))
        return JS::NullValue();

    if (!IsTexParamValid(pname)) {
        ErrorInvalidEnumInfo("pname", pname);
        return JS::NullValue();
    }

    return tex->GetTexParameter(texTarget, pname);
}
void
WebGLContext::TexSubImage2D(GLenum rawTexImageTarget, GLint level, GLint xOffset,
                            GLint yOffset, GLsizei width, GLsizei height,
                            GLenum unpackFormat, GLenum unpackType,
                            const dom::Nullable<dom::ArrayBufferView>& maybeView,
                            ErrorResult& out_rv)
{
    TexImageTarget texImageTarget;
    WebGLTexture* tex;
    if (!ValidateTexImageTarget(this, rawTexImageTarget, "texSubImage2D", &texImageTarget,
                                &tex))
    {
        return;
    }

    tex->TexSubImage2D(texImageTarget, level, xOffset, yOffset, width, height,
                       unpackFormat, unpackType, maybeView, &out_rv);
}
ScopedResolveTexturesForDraw::ScopedResolveTexturesForDraw(WebGLContext* webgl,
                                                           const char* funcName,
                                                           bool* const out_error)
    : mWebGL(webgl)
{
    MOZ_ASSERT(webgl->gl->IsCurrent());

    typedef decltype(WebGLContext::mBound2DTextures) TexturesT;

    const auto fnResolveAll = [this, funcName](const TexturesT& textures)
    {
        const auto len = textures.Length();
        for (uint32_t texUnit = 0; texUnit < len; ++texUnit) {
            WebGLTexture* tex = textures[texUnit];
            if (!tex)
                continue;

            FakeBlackType fakeBlack;
            if (!tex->ResolveForDraw(funcName, texUnit, &fakeBlack))
                return false;

            if (fakeBlack == FakeBlackType::None)
                continue;

            mWebGL->BindFakeBlack(texUnit, tex->Target(), fakeBlack);
            mRebindRequests.push_back({texUnit, tex});
        }

        return true;
    };

    bool ok = true;
    ok &= fnResolveAll(mWebGL->mBound2DTextures);
    ok &= fnResolveAll(mWebGL->mBoundCubeMapTextures);
    ok &= fnResolveAll(mWebGL->mBound3DTextures);
    ok &= fnResolveAll(mWebGL->mBound2DArrayTextures);

    if (!ok) {
        mWebGL->ErrorOutOfMemory("%s: Failed to resolve textures for draw.", funcName);
    }

    *out_error = !ok;
}
/** Validates parameters to texStorage{2D,3D} */
bool
WebGL2Context::ValidateTexStorage(GLenum target, GLsizei levels, GLenum internalformat,
                                      GLsizei width, GLsizei height, GLsizei depth,
                                      const char* info)
{
    // GL_INVALID_OPERATION is generated if the default texture object is curently bound to target.
    WebGLTexture* tex = activeBoundTextureForTarget(target);
    if (!tex) {
        ErrorInvalidOperation("%s: no texture is bound to target %s", info, EnumName(target));
        return false;
    }

    // GL_INVALID_OPERATION is generated if the texture object currently bound to target already has
    // GL_TEXTURE_IMMUTABLE_FORMAT set to GL_TRUE.
    if (tex->IsImmutable()) {
        ErrorInvalidOperation("%s: texture bound to target %s is already immutable", info, EnumName(target));
        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)  { ErrorInvalidValue("%s: width is < 1", info);  return false; }
    if (height < 1) { ErrorInvalidValue("%s: height is < 1", info); return false; }
    if (depth < 1)  { ErrorInvalidValue("%s: depth is < 1", info);  return false; }
    if (levels < 1) { 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) {
        ErrorInvalidOperation("%s: too many levels for given texture dimensions", info);
        return false;
    }

    return true;
}
void
WebGL2Context::TexStorage3D(GLenum target, GLsizei levels, GLenum internalformat,
                            GLsizei width, GLsizei height, GLsizei depth)
{
    if (IsContextLost())
        return;

    // GL_INVALID_ENUM is generated if target is not one of the accepted target enumerants.
    if (target != LOCAL_GL_TEXTURE_3D)
        return ErrorInvalidEnum("texStorage3D: target is not TEXTURE_3D");

    if (!ValidateTexStorage(target, levels, internalformat, width, height, depth, "texStorage3D"))
        return;

    GetAndFlushUnderlyingGLErrors();
    gl->fTexStorage3D(target, levels, internalformat, width, height, depth);
    GLenum error = GetAndFlushUnderlyingGLErrors();
    if (error) {
        return GenerateWarning("texStorage3D generated error %s", ErrorName(error));
    }

    WebGLTexture* tex = activeBoundTextureForTarget(target);
    tex->SetImmutable();

    GLsizei w = width;
    GLsizei h = height;
    GLsizei d = depth;
    for (size_t l = 0; l < size_t(levels); l++) {
        tex->SetImageInfo(TexImageTargetForTargetAndFace(target, 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
WebGL2Context::TexStorage2D(GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height)
{
    if (IsContextLost())
        return;

    // GL_INVALID_ENUM is generated if target is not one of the accepted target enumerants.
    if (target != LOCAL_GL_TEXTURE_2D && target != LOCAL_GL_TEXTURE_CUBE_MAP)
        return ErrorInvalidEnum("texStorage2D: target is not TEXTURE_2D or TEXTURE_CUBE_MAP");

    if (!ValidateTexStorage(target, levels, internalformat, width, height, 1, "texStorage2D"))
        return;

    GetAndFlushUnderlyingGLErrors();
    gl->fTexStorage2D(target, levels, internalformat, width, height);
    GLenum error = GetAndFlushUnderlyingGLErrors();
    if (error) {
        return GenerateWarning("texStorage2D generated error %s", ErrorName(error));
    }

    WebGLTexture* tex = activeBoundTextureForTarget(target);
    tex->SetImmutable();

    const size_t facesCount = (target == 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++) {
            tex->SetImageInfo(TexImageTargetForTargetAndFace(target, f),
                              l, w, h, 1,
                              internalformat,
                              WebGLImageDataStatus::UninitializedImageData);
        }
        w = std::max(1, w / 2);
        h = std::max(1, h / 2);
    }
}
void
WebGL2Context::TexSubImage3D(GLenum rawTarget, GLint level,
                             GLint xoffset, GLint yoffset, GLint zoffset,
                             GLsizei width, GLsizei height, GLsizei depth,
                             GLenum format, GLenum type, const Nullable<dom::ArrayBufferView>& pixels,
                             ErrorResult& rv)
{
    if (IsContextLost())
        return;

    if (pixels.IsNull())
        return ErrorInvalidValue("texSubImage3D: pixels must not be null!");

    const ArrayBufferView& view = pixels.Value();
    view.ComputeLengthAndData();

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

    if (!ValidateTexImageTarget(rawTarget, func, dims))
        return;

    TexImageTarget texImageTarget(rawTarget);

    WebGLTexture* tex = activeBoundTextureForTexImageTarget(texImageTarget);
    if (!tex) {
        return ErrorInvalidOperation("texSubImage3D: no texture bound on active texture unit");
    }

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

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

    if (!ValidateTexImage(texImageTarget, level, existingEffectiveInternalFormat.get(),
                          xoffset, yoffset, zoffset,
                          width, height, depth,
                          0, format, type, func, dims))
    {
        return;
    }

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

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

    if (!ValidateTexInputData(type, 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 =
        GetImageSize(height, width, depth, srcTexelSize, mPixelStoreUnpackAlignment);

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

    uint32_t bytesNeeded = checked_neededByteLength.value();

    if (dataLength < bytesNeeded)
        return 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) {
            tex->SetImageDataStatus(texImageTarget, level, WebGLImageDataStatus::InitializedImageData);
        } else {
            tex->EnsureNoUninitializedImageData(texImageTarget, level);
        }
    }

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

    MakeContextCurrent();
    gl->fTexSubImage3D(texImageTarget.get(), level,
                       xoffset, yoffset, zoffset,
                       width, height, depth,
                       driverFormat, driverType, data);

}
void
WebGL2Context::TexImage3D(GLenum target, GLint level, GLenum internalformat,
                          GLsizei width, GLsizei height, GLsizei depth,
                          GLint border, GLenum format, GLenum type,
                          const Nullable<dom::ArrayBufferView> &pixels,
                          ErrorResult& rv)
{
    if (IsContextLost())
        return;

    void* data;
    size_t dataLength;
    js::Scalar::Type jsArrayType;
    if (pixels.IsNull()) {
        data = nullptr;
        dataLength = 0;
        jsArrayType = js::Scalar::TypeMax;
    } else {
        const ArrayBufferView& view = pixels.Value();
        view.ComputeLengthAndData();

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

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

    if (!ValidateTexImageTarget(target, func, dims))
        return;

    TexImageTarget texImageTarget = target;

    if (!ValidateTexImage(texImageTarget, level, internalformat,
                          0, 0, 0,
                          width, height, depth,
                          border, format, type, func, dims))
    {
        return;
    }

    if (!ValidateTexInputData(type, jsArrayType, func, dims))
        return;

    TexInternalFormat effectiveInternalFormat =
        EffectiveInternalFormatFromInternalFormatAndType(internalformat, type);

    if (effectiveInternalFormat == LOCAL_GL_NONE) {
        return ErrorInvalidOperation("texImage3D: bad combination of internalformat and type");
    }

    // 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(format, type);
    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.
    size_t srcTexelSize = srcbitsPerTexel / 8;

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

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

    uint32_t bytesNeeded = checked_neededByteLength.value();

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

    WebGLTexture* tex = activeBoundTextureForTexImageTarget(texImageTarget);

    if (!tex)
        return ErrorInvalidOperation("texImage3D: no texture is bound to this target");

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

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

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

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