dom/canvas/WebGLContextValidate.cpp
author Sotaro Ikeda <sikeda@mozilla.com>
Tue, 03 Mar 2015 08:51:05 -0800
changeset 250242 610aae9b5e36906b303dde44af508a1c30d1675c
parent 248937 6dab2820469ae49c71d3bd7045d44a915810f7b5
child 250559 32e93a36551d133c126ac024d88d99962ea53c84
permissions -rw-r--r--
Bug 1137251 - Disable RemoveTextureFromCompositableTracker except gonk. r=nical, a=lizzard

/* -*- 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 "WebGLContext.h"

#include <algorithm>
#include "angle/ShaderLang.h"
#include "CanvasUtils.h"
#include "GLContext.h"
#include "jsfriendapi.h"
#include "mozilla/CheckedInt.h"
#include "mozilla/Preferences.h"
#include "mozilla/Services.h"
#include "nsIObserverService.h"
#include "WebGLBuffer.h"
#include "WebGLContextUtils.h"
#include "WebGLFramebuffer.h"
#include "WebGLProgram.h"
#include "WebGLRenderbuffer.h"
#include "WebGLShader.h"
#include "WebGLTexture.h"
#include "WebGLUniformLocation.h"
#include "WebGLVertexArray.h"
#include "WebGLVertexAttribData.h"

#if defined(MOZ_WIDGET_COCOA)
#include "nsCocoaFeatures.h"
#endif

namespace mozilla {

/**
 * Return the block size for format.
 */
static void
BlockSizeFor(GLenum format, GLint* const out_blockWidth,
             GLint* const out_blockHeight)
{
    MOZ_ASSERT(out_blockWidth && out_blockHeight);

    switch (format) {
    case LOCAL_GL_ATC_RGB:
    case LOCAL_GL_ATC_RGBA_EXPLICIT_ALPHA:
    case LOCAL_GL_ATC_RGBA_INTERPOLATED_ALPHA:
    case LOCAL_GL_COMPRESSED_RGB_S3TC_DXT1_EXT:
    case LOCAL_GL_COMPRESSED_RGBA_S3TC_DXT1_EXT:
    case LOCAL_GL_COMPRESSED_RGBA_S3TC_DXT3_EXT:
    case LOCAL_GL_COMPRESSED_RGBA_S3TC_DXT5_EXT:
        *out_blockWidth = 4;
        *out_blockHeight = 4;
        break;

    case LOCAL_GL_ETC1_RGB8_OES:
        // 4x4 blocks, but no 4-multiple requirement.
        break;

    default:
        break;
    }
}

static bool
IsCompressedFunc(WebGLTexImageFunc func)
{
    return func == WebGLTexImageFunc::CompTexImage ||
           func == WebGLTexImageFunc::CompTexSubImage;
}

/**
 * Same as ErrorInvalidEnum but uses WebGLContext::EnumName to print displayable
 * name for \a glenum.
 */
static void
ErrorInvalidEnumWithName(WebGLContext* ctx, const char* msg, GLenum glenum,
                         WebGLTexImageFunc func, WebGLTexDimensions dims)
{
    const char* name = WebGLContext::EnumName(glenum);
    if (name) {
        ctx->ErrorInvalidEnum("%s: %s %s", InfoFrom(func, dims), msg, name);
    } else {
        ctx->ErrorInvalidEnum("%s: %s 0x%04x", InfoFrom(func, dims), msg,
                              glenum);
    }
}

/**
 * Return true if the format is valid for source calls.
 */
static bool
IsAllowedFromSource(GLenum format, WebGLTexImageFunc func)
{
    switch (format) {
    case LOCAL_GL_COMPRESSED_RGB_S3TC_DXT1_EXT:
    case LOCAL_GL_COMPRESSED_RGBA_S3TC_DXT1_EXT:
    case LOCAL_GL_COMPRESSED_RGBA_S3TC_DXT3_EXT:
    case LOCAL_GL_COMPRESSED_RGBA_S3TC_DXT5_EXT:
    case LOCAL_GL_COMPRESSED_RGB_PVRTC_2BPPV1:
    case LOCAL_GL_COMPRESSED_RGB_PVRTC_4BPPV1:
    case LOCAL_GL_COMPRESSED_RGBA_PVRTC_2BPPV1:
    case LOCAL_GL_COMPRESSED_RGBA_PVRTC_4BPPV1:
        return (func == WebGLTexImageFunc::CompTexImage ||
                func == WebGLTexImageFunc::CompTexSubImage);

    case LOCAL_GL_ATC_RGB:
    case LOCAL_GL_ATC_RGBA_EXPLICIT_ALPHA:
    case LOCAL_GL_ATC_RGBA_INTERPOLATED_ALPHA:
    case LOCAL_GL_ETC1_RGB8_OES:
        return func == WebGLTexImageFunc::CompTexImage;
    }

    return true;
}

/**
 * Returns true if func is a CopyTexImage variant.
 */
static bool
IsCopyFunc(WebGLTexImageFunc func)
{
    return (func == WebGLTexImageFunc::CopyTexImage ||
            func == WebGLTexImageFunc::CopyTexSubImage);
}

/**
 * Returns true if func is a SubImage variant.
 */
static bool
IsSubFunc(WebGLTexImageFunc func)
{
    return (func == WebGLTexImageFunc::TexSubImage ||
            func == WebGLTexImageFunc::CopyTexSubImage ||
            func == WebGLTexImageFunc::CompTexSubImage);
}

/**
 * returns true is target is a texture cube map target.
 */
static bool
IsTexImageCubemapTarget(GLenum texImageTarget)
{
    return (texImageTarget >= LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_X &&
            texImageTarget <= LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Z);
}

bool
WebGLContext::ValidateBlendEquationEnum(GLenum mode, const char* info)
{
    switch (mode) {
    case LOCAL_GL_FUNC_ADD:
    case LOCAL_GL_FUNC_SUBTRACT:
    case LOCAL_GL_FUNC_REVERSE_SUBTRACT:
        return true;

    case LOCAL_GL_MIN:
    case LOCAL_GL_MAX:
        if (IsExtensionEnabled(WebGLExtensionID::EXT_blend_minmax))
            return true;

        break;

    default:
        break;
    }

    ErrorInvalidEnumInfo(info, mode);
    return false;
}

bool
WebGLContext::ValidateBlendFuncDstEnum(GLenum factor, const char* info)
{
    switch (factor) {
    case LOCAL_GL_ZERO:
    case LOCAL_GL_ONE:
    case LOCAL_GL_SRC_COLOR:
    case LOCAL_GL_ONE_MINUS_SRC_COLOR:
    case LOCAL_GL_DST_COLOR:
    case LOCAL_GL_ONE_MINUS_DST_COLOR:
    case LOCAL_GL_SRC_ALPHA:
    case LOCAL_GL_ONE_MINUS_SRC_ALPHA:
    case LOCAL_GL_DST_ALPHA:
    case LOCAL_GL_ONE_MINUS_DST_ALPHA:
    case LOCAL_GL_CONSTANT_COLOR:
    case LOCAL_GL_ONE_MINUS_CONSTANT_COLOR:
    case LOCAL_GL_CONSTANT_ALPHA:
    case LOCAL_GL_ONE_MINUS_CONSTANT_ALPHA:
        return true;

    default:
        ErrorInvalidEnumInfo(info, factor);
        return false;
    }
}

bool
WebGLContext::ValidateBlendFuncSrcEnum(GLenum factor, const char* info)
{
    if (factor == LOCAL_GL_SRC_ALPHA_SATURATE)
        return true;

    return ValidateBlendFuncDstEnum(factor, info);
}

bool
WebGLContext::ValidateBlendFuncEnumsCompatibility(GLenum sfactor,
                                                  GLenum dfactor,
                                                  const char* info)
{
    bool sfactorIsConstantColor = sfactor == LOCAL_GL_CONSTANT_COLOR ||
                                  sfactor == LOCAL_GL_ONE_MINUS_CONSTANT_COLOR;
    bool sfactorIsConstantAlpha = sfactor == LOCAL_GL_CONSTANT_ALPHA ||
                                  sfactor == LOCAL_GL_ONE_MINUS_CONSTANT_ALPHA;
    bool dfactorIsConstantColor = dfactor == LOCAL_GL_CONSTANT_COLOR ||
                                  dfactor == LOCAL_GL_ONE_MINUS_CONSTANT_COLOR;
    bool dfactorIsConstantAlpha = dfactor == LOCAL_GL_CONSTANT_ALPHA ||
                                  dfactor == LOCAL_GL_ONE_MINUS_CONSTANT_ALPHA;
    if ( (sfactorIsConstantColor && dfactorIsConstantAlpha) ||
         (dfactorIsConstantColor && sfactorIsConstantAlpha) )
    {
        ErrorInvalidOperation("%s are mutually incompatible, see section 6.8 in"
                              " the WebGL 1.0 spec", info);
        return false;
    }

    return true;
}

bool
WebGLContext::ValidateDataOffsetSize(WebGLintptr offset, WebGLsizeiptr size, WebGLsizeiptr bufferSize, const char* info)
{
    if (offset < 0) {
        ErrorInvalidValue("%s: offset must be positive", info);
        return false;
    }

    if (size < 0) {
        ErrorInvalidValue("%s: size must be positive", info);
        return false;
    }

    // *** Careful *** WebGLsizeiptr is always 64-bits but GLsizeiptr
    // is like intptr_t. On some platforms it is 32-bits.
    CheckedInt<GLsizeiptr> neededBytes = CheckedInt<GLsizeiptr>(offset) + size;
    if (!neededBytes.isValid() || neededBytes.value() > bufferSize) {
        ErrorInvalidValue("%s: invalid range", info);
        return false;
    }

    return true;
}

/**
 * Check data ranges [readOffset, readOffset + size] and [writeOffset,
 * writeOffset + size] for overlap.
 *
 * It is assumed that offset and size have already been validated with
 * ValidateDataOffsetSize().
 */
bool
WebGLContext::ValidateDataRanges(WebGLintptr readOffset, WebGLintptr writeOffset, WebGLsizeiptr size, const char* info)
{
    MOZ_ASSERT((CheckedInt<WebGLsizeiptr>(readOffset) + size).isValid());
    MOZ_ASSERT((CheckedInt<WebGLsizeiptr>(writeOffset) + size).isValid());

    bool separate = (readOffset + size < writeOffset || writeOffset + size < readOffset);
    if (!separate)
        ErrorInvalidValue("%s: ranges [readOffset, readOffset + size) and [writeOffset, writeOffset + size) overlap");

    return separate;
}

bool
WebGLContext::ValidateTextureTargetEnum(GLenum target, const char* info)
{
    switch (target) {
    case LOCAL_GL_TEXTURE_2D:
    case LOCAL_GL_TEXTURE_CUBE_MAP:
        return true;

    case LOCAL_GL_TEXTURE_3D:
        if (IsWebGL2())
            return true;

        break;

    default:
        break;
    }

    ErrorInvalidEnumInfo(info, target);
    return false;
}

bool
WebGLContext::ValidateComparisonEnum(GLenum target, const char* info)
{
    switch (target) {
    case LOCAL_GL_NEVER:
    case LOCAL_GL_LESS:
    case LOCAL_GL_LEQUAL:
    case LOCAL_GL_GREATER:
    case LOCAL_GL_GEQUAL:
    case LOCAL_GL_EQUAL:
    case LOCAL_GL_NOTEQUAL:
    case LOCAL_GL_ALWAYS:
        return true;

    default:
        ErrorInvalidEnumInfo(info, target);
        return false;
    }
}

bool
WebGLContext::ValidateStencilOpEnum(GLenum action, const char* info)
{
    switch (action) {
    case LOCAL_GL_KEEP:
    case LOCAL_GL_ZERO:
    case LOCAL_GL_REPLACE:
    case LOCAL_GL_INCR:
    case LOCAL_GL_INCR_WRAP:
    case LOCAL_GL_DECR:
    case LOCAL_GL_DECR_WRAP:
    case LOCAL_GL_INVERT:
        return true;

    default:
        ErrorInvalidEnumInfo(info, action);
        return false;
    }
}

bool
WebGLContext::ValidateFaceEnum(GLenum face, const char* info)
{
    switch (face) {
    case LOCAL_GL_FRONT:
    case LOCAL_GL_BACK:
    case LOCAL_GL_FRONT_AND_BACK:
        return true;

    default:
        ErrorInvalidEnumInfo(info, face);
        return false;
    }
}

bool
WebGLContext::ValidateDrawModeEnum(GLenum mode, const char* info)
{
    switch (mode) {
    case LOCAL_GL_TRIANGLES:
    case LOCAL_GL_TRIANGLE_STRIP:
    case LOCAL_GL_TRIANGLE_FAN:
    case LOCAL_GL_POINTS:
    case LOCAL_GL_LINE_STRIP:
    case LOCAL_GL_LINE_LOOP:
    case LOCAL_GL_LINES:
        return true;

    default:
        ErrorInvalidEnumInfo(info, mode);
        return false;
    }
}

bool
WebGLContext::ValidateGLSLVariableName(const nsAString& name, const char* info)
{
    if (name.IsEmpty())
        return false;

    const uint32_t maxSize = 256;
    if (name.Length() > maxSize) {
        ErrorInvalidValue("%s: Identifier is %d characters long, exceeds the"
                          " maximum allowed length of %d characters.", info,
                          name.Length(), maxSize);
        return false;
    }

    if (!ValidateGLSLString(name, info))
        return false;

    nsString prefix1 = NS_LITERAL_STRING("webgl_");
    nsString prefix2 = NS_LITERAL_STRING("_webgl_");

    if (Substring(name, 0, prefix1.Length()).Equals(prefix1) ||
        Substring(name, 0, prefix2.Length()).Equals(prefix2))
    {
        ErrorInvalidOperation("%s: String contains a reserved GLSL prefix.",
                              info);
        return false;
    }

    return true;
}

bool WebGLContext::ValidateGLSLString(const nsAString& string, const char* info)
{
    for (uint32_t i = 0; i < string.Length(); ++i) {
        if (!ValidateGLSLCharacter(string.CharAt(i))) {
             ErrorInvalidValue("%s: String contains the illegal character"
                               " '%d'.", info, string.CharAt(i));
             return false;
        }
    }

    return true;
}

/**
 * Return true if the framebuffer attachment is valid. Attachment must
 * be one of depth/stencil/depth_stencil/color attachment.
 */
bool
WebGLContext::ValidateFramebufferAttachment(GLenum attachment,
                                            const char* funcName)
{
    if (!mBoundFramebuffer) {
        switch (attachment) {
        case LOCAL_GL_COLOR:
        case LOCAL_GL_DEPTH:
        case LOCAL_GL_STENCIL:
            return true;

        default:
            ErrorInvalidEnum("%s: attachment: invalid enum value 0x%x.",
                             funcName, attachment);
            return false;
        }
    }

    if (attachment == LOCAL_GL_DEPTH_ATTACHMENT ||
        attachment == LOCAL_GL_STENCIL_ATTACHMENT ||
        attachment == LOCAL_GL_DEPTH_STENCIL_ATTACHMENT)
    {
        return true;
    }

    GLenum colorAttachCount = 1;
    if (IsExtensionEnabled(WebGLExtensionID::WEBGL_draw_buffers))
        colorAttachCount = mGLMaxColorAttachments;

    if (attachment >= LOCAL_GL_COLOR_ATTACHMENT0 &&
        attachment < GLenum(LOCAL_GL_COLOR_ATTACHMENT0 + colorAttachCount))
    {
        return true;
    }

    ErrorInvalidEnum("%s: attachment: invalid enum value 0x%x.", funcName,
                     attachment);
    return false;
}

/**
 * Return true if pname is valid for GetSamplerParameter calls.
 */
bool
WebGLContext::ValidateSamplerParameterName(GLenum pname, const char* info)
{
    switch (pname) {
    case LOCAL_GL_TEXTURE_MIN_FILTER:
    case LOCAL_GL_TEXTURE_MAG_FILTER:
    case LOCAL_GL_TEXTURE_WRAP_S:
    case LOCAL_GL_TEXTURE_WRAP_T:
    case LOCAL_GL_TEXTURE_WRAP_R:
    case LOCAL_GL_TEXTURE_MIN_LOD:
    case LOCAL_GL_TEXTURE_MAX_LOD:
    case LOCAL_GL_TEXTURE_COMPARE_MODE:
    case LOCAL_GL_TEXTURE_COMPARE_FUNC:
        return true;

    default:
        ErrorInvalidEnum("%s: invalid pname: %s", info, EnumName(pname));
        return false;
    }
}

/**
 * Return true if pname and param are valid combination for SamplerParameter calls.
 */
bool
WebGLContext::ValidateSamplerParameterParams(GLenum pname, const WebGLIntOrFloat& param, const char* info)
{
    const GLenum p = param.AsInt();

    switch (pname) {
    case LOCAL_GL_TEXTURE_MIN_FILTER:
        switch (p) {
        case LOCAL_GL_NEAREST:
        case LOCAL_GL_LINEAR:
        case LOCAL_GL_NEAREST_MIPMAP_NEAREST:
        case LOCAL_GL_NEAREST_MIPMAP_LINEAR:
        case LOCAL_GL_LINEAR_MIPMAP_NEAREST:
        case LOCAL_GL_LINEAR_MIPMAP_LINEAR:
            return true;

        default:
            ErrorInvalidEnum("%s: invalid param: %s", info, EnumName(p));
            return false;
        }

    case LOCAL_GL_TEXTURE_MAG_FILTER:
        switch (p) {
        case LOCAL_GL_NEAREST:
        case LOCAL_GL_LINEAR:
            return true;

        default:
            ErrorInvalidEnum("%s: invalid param: %s", info, EnumName(p));
            return false;
        }

    case LOCAL_GL_TEXTURE_WRAP_S:
    case LOCAL_GL_TEXTURE_WRAP_T:
    case LOCAL_GL_TEXTURE_WRAP_R:
        switch (p) {
        case LOCAL_GL_CLAMP_TO_EDGE:
        case LOCAL_GL_REPEAT:
        case LOCAL_GL_MIRRORED_REPEAT:
            return true;

        default:
            ErrorInvalidEnum("%s: invalid param: %s", info, EnumName(p));
            return false;
        }

    case LOCAL_GL_TEXTURE_MIN_LOD:
    case LOCAL_GL_TEXTURE_MAX_LOD:
        return true;

    case LOCAL_GL_TEXTURE_COMPARE_MODE:
        switch (param.AsInt()) {
        case LOCAL_GL_NONE:
        case LOCAL_GL_COMPARE_REF_TO_TEXTURE:
            return true;

        default:
            ErrorInvalidEnum("%s: invalid param: %s", info, EnumName(p));
            return false;
        }

    case LOCAL_GL_TEXTURE_COMPARE_FUNC:
        switch (p) {
        case LOCAL_GL_LEQUAL:
        case LOCAL_GL_GEQUAL:
        case LOCAL_GL_LESS:
        case LOCAL_GL_GREATER:
        case LOCAL_GL_EQUAL:
        case LOCAL_GL_NOTEQUAL:
        case LOCAL_GL_ALWAYS:
        case LOCAL_GL_NEVER:
            return true;

        default:
            ErrorInvalidEnum("%s: invalid param: %s", info, EnumName(p));
            return false;
        }

    default:
        ErrorInvalidEnum("%s: invalid pname: %s", info, EnumName(pname));
        return false;
    }
}


/**
 * Return true if format is a valid texture image format for source,
 * taking into account enabled WebGL extensions.
 */
bool
WebGLContext::ValidateTexImageFormat(GLenum format, WebGLTexImageFunc func,
                                     WebGLTexDimensions dims)
{
    /* Core WebGL texture formats */
    if (format == LOCAL_GL_ALPHA ||
        format == LOCAL_GL_RGB ||
        format == LOCAL_GL_RGBA ||
        format == LOCAL_GL_LUMINANCE ||
        format == LOCAL_GL_LUMINANCE_ALPHA)
    {
        return true;
    }

    /* WebGL2 new formats */
    if (format == LOCAL_GL_RED ||
        format == LOCAL_GL_RG ||
        format == LOCAL_GL_RED_INTEGER ||
        format == LOCAL_GL_RG_INTEGER ||
        format == LOCAL_GL_RGB_INTEGER ||
        format == LOCAL_GL_RGBA_INTEGER)
    {
        if (IsWebGL2())
            return true;

        ErrorInvalidEnum("%s: Invalid format %s: Requires WebGL version 2.0 or"
                         " newer.", InfoFrom(func, dims), EnumName(format));
        return false;
    }

    /* WEBGL_depth_texture added formats */
    if (format == LOCAL_GL_DEPTH_COMPONENT ||
        format == LOCAL_GL_DEPTH_STENCIL)
    {
        if (!IsExtensionEnabled(WebGLExtensionID::WEBGL_depth_texture)) {
            ErrorInvalidEnum("%s: Invalid format %s: Requires that"
                             " WEBGL_depth_texture is enabled.",
                             InfoFrom(func, dims), EnumName(format));
            return false;
        }

        // If WEBGL_depth_texture is enabled, then it is not allowed to be used
        // with the copyTexImage, or copyTexSubImage methods, and it is not
        // allowed with texSubImage in WebGL1.
        if ((func == WebGLTexImageFunc::TexSubImage && !IsWebGL2()) ||
            func == WebGLTexImageFunc::CopyTexImage ||
            func == WebGLTexImageFunc::CopyTexSubImage)
        {
            ErrorInvalidOperation("%s: format %s is not supported",
                                  InfoFrom(func, dims), EnumName(format));
            return false;
        }

        return true;
    }

    // Needs to be below the depth_texture check because an invalid operation
    // error needs to be generated instead of invalid enum.
    // Only core formats are valid for CopyTex[Sub]Image.
    // TODO: Revisit this once color_buffer_[half_]float lands.
    if (IsCopyFunc(func)) {
        ErrorInvalidEnumWithName(this, "invalid format", format, func, dims);
        return false;
    }

    // EXT_sRGB added formats
    if (format == LOCAL_GL_SRGB ||
        format == LOCAL_GL_SRGB_ALPHA)
    {
        if (IsExtensionEnabled(WebGLExtensionID::EXT_sRGB))
            return true;

        ErrorInvalidEnum("%s: Invalid format %s: Requires that EXT_sRGB is"
                         " enabled.", InfoFrom(func, dims),
                         WebGLContext::EnumName(format));
        return false;
    }

    ErrorInvalidEnumWithName(this, "invalid format", format, func, dims);
    return false;
}

/**
 * Check if the given texture target is valid for TexImage.
 */
bool
WebGLContext::ValidateTexImageTarget(GLenum target, WebGLTexImageFunc func,
                                     WebGLTexDimensions dims)
{
    switch (dims) {
    case WebGLTexDimensions::Tex2D:
        if (target == LOCAL_GL_TEXTURE_2D ||
            IsTexImageCubemapTarget(target))
        {
            return true;
        }

        ErrorInvalidEnumWithName(this, "invalid target", target, func, dims);
        return false;

    case WebGLTexDimensions::Tex3D:
        if (target == LOCAL_GL_TEXTURE_3D)
        {
            return true;
        }

        ErrorInvalidEnumWithName(this, "invalid target", target, func, dims);
        return false;

    default:
        MOZ_ASSERT(false, "ValidateTexImageTarget: Invalid dims");
    }

    return false;
}

/**
 * Return true if type is a valid texture image type for source,
 * taking into account enabled WebGL extensions.
 */
bool
WebGLContext::ValidateTexImageType(GLenum type, WebGLTexImageFunc func,
                                   WebGLTexDimensions dims)
{
    /* Core WebGL texture types */
    if (type == LOCAL_GL_UNSIGNED_BYTE ||
        type == LOCAL_GL_UNSIGNED_SHORT_5_6_5 ||
        type == LOCAL_GL_UNSIGNED_SHORT_4_4_4_4 ||
        type == LOCAL_GL_UNSIGNED_SHORT_5_5_5_1)
    {
        return true;
    }

    /* WebGL2 new types */
    if (type == LOCAL_GL_BYTE ||
        type == LOCAL_GL_SHORT ||
        type == LOCAL_GL_INT ||
        type == LOCAL_GL_FLOAT_32_UNSIGNED_INT_24_8_REV ||
        type == LOCAL_GL_UNSIGNED_INT_2_10_10_10_REV ||
        type == LOCAL_GL_UNSIGNED_INT_10F_11F_11F_REV ||
        type == LOCAL_GL_UNSIGNED_INT_5_9_9_9_REV)
    {
        if (IsWebGL2())
            return true;

        ErrorInvalidEnum("%s: Invalid type %s: Requires WebGL version 2.0 or"
                         " newer.", InfoFrom(func, dims),
                         WebGLContext::EnumName(type));
        return false;
    }

    /* OES_texture_float added types */
    if (type == LOCAL_GL_FLOAT) {
        if (IsExtensionEnabled(WebGLExtensionID::OES_texture_float))
            return true;

        ErrorInvalidEnum("%s: Invalid type %s: Requires that OES_texture_float"
                         " is enabled.",
                         InfoFrom(func, dims), WebGLContext::EnumName(type));
        return false;
    }

    /* OES_texture_half_float add types */
    if (type == LOCAL_GL_HALF_FLOAT) {
        if (IsExtensionEnabled(WebGLExtensionID::OES_texture_half_float))
            return true;

        ErrorInvalidEnum("%s: Invalid type %s: Requires that"
                         " OES_texture_half_float is enabled.",
                         InfoFrom(func, dims), WebGLContext::EnumName(type));
        return false;
    }

    /* WEBGL_depth_texture added types */
    if (type == LOCAL_GL_UNSIGNED_SHORT ||
        type == LOCAL_GL_UNSIGNED_INT ||
        type == LOCAL_GL_UNSIGNED_INT_24_8)
    {
        if (IsExtensionEnabled(WebGLExtensionID::WEBGL_depth_texture))
            return true;

        ErrorInvalidEnum("%s: Invalid type %s: Requires that"
                         " WEBGL_depth_texture is enabled.",
                         InfoFrom(func, dims), WebGLContext::EnumName(type));
        return false;
    }

    ErrorInvalidEnumWithName(this, "invalid type", type, func, dims);
    return false;
}

/**
 * Validate texture image sizing extra constraints for
 * CompressedTex(Sub)?Image.
 */
// TODO: WebGL 2
bool
WebGLContext::ValidateCompTexImageSize(GLint level, GLenum format,
                                       GLint xoffset, GLint yoffset,
                                       GLsizei width, GLsizei height,
                                       GLsizei levelWidth, GLsizei levelHeight,
                                       WebGLTexImageFunc func,
                                       WebGLTexDimensions dims)
{
    // Negative parameters must already have been handled above
    MOZ_ASSERT(xoffset >= 0 && yoffset >= 0 &&
               width >= 0 && height >= 0);

    if (xoffset + width > (GLint) levelWidth) {
        ErrorInvalidValue("%s: xoffset + width must be <= levelWidth.",
                          InfoFrom(func, dims));
        return false;
    }

    if (yoffset + height > (GLint) levelHeight) {
        ErrorInvalidValue("%s: yoffset + height must be <= levelHeight.",
                          InfoFrom(func, dims));
        return false;
    }

    GLint blockWidth = 1;
    GLint blockHeight = 1;
    BlockSizeFor(format, &blockWidth, &blockHeight);

    // If blockWidth || blockHeight != 1, then the compressed format had
    // block-based constraints to be checked. (For example, PVRTC is compressed
    // but isn't a block-based format)
    if (blockWidth != 1 || blockHeight != 1) {
        // Offsets must be multiple of block size.
        if (xoffset % blockWidth != 0) {
            ErrorInvalidOperation("%s: xoffset must be multiple of %d.",
                                  InfoFrom(func, dims), blockWidth);
            return false;
        }

        if (yoffset % blockHeight != 0) {
            ErrorInvalidOperation("%s: yoffset must be multiple of %d.",
                                  InfoFrom(func, dims), blockHeight);
            return false;
        }

        /* The size must be a multiple of blockWidth and blockHeight, or must be
         * using offset+size that exactly hits the edge. Important for small
         * mipmap levels.
         *
         * From the WEBGL_compressed_texture_s3tc spec:
         *     When level equals zero width and height must be a multiple of 4.
         *     When level is greater than 0 width and height must be 0, 1, 2 or
         *     a multiple of 4. If they are not an INVALID_OPERATION error is
         *     generated."
         */
        if (level == 0) {
            if (width % blockWidth != 0) {
                ErrorInvalidOperation("%s: Width of level 0 must be a multiple"
                                      " of %d.", InfoFrom(func, dims),
                                      blockWidth);
                return false;
            }

            if (height % blockHeight != 0) {
                ErrorInvalidOperation("%s: Height of level 0 must be a multiple"
                                      " of %d.", InfoFrom(func, dims),
                                      blockHeight);
                return false;
            }
        } else if (level > 0) {
            if (width % blockWidth != 0 && width > 2) {
                ErrorInvalidOperation("%s: Width of level %d must be a multiple"
                                      " of %d, or be 0, 1, or 2.",
                                      InfoFrom(func, dims), level, blockWidth);
                return false;
            }

            if (height % blockHeight != 0 && height > 2) {
                ErrorInvalidOperation("%s: Height of level %d must be a"
                                      " multiple of %d, or be 0, 1, or 2.",
                                      InfoFrom(func, dims), level, blockHeight);
                return false;
            }
        }

        if (IsSubFunc(func)) {
            if ((xoffset % blockWidth) != 0) {
                ErrorInvalidOperation("%s: xoffset must be a multiple of %d.",
                                      InfoFrom(func, dims), blockWidth);
                return false;
            }

            if (yoffset % blockHeight != 0) {
                ErrorInvalidOperation("%s: yoffset must be a multiple of %d.",
                                      InfoFrom(func, dims), blockHeight);
                return false;
            }
        }
    }

    switch (format) {
    case LOCAL_GL_COMPRESSED_RGB_PVRTC_4BPPV1:
    case LOCAL_GL_COMPRESSED_RGB_PVRTC_2BPPV1:
    case LOCAL_GL_COMPRESSED_RGBA_PVRTC_4BPPV1:
    case LOCAL_GL_COMPRESSED_RGBA_PVRTC_2BPPV1:
        if (!IsPOTAssumingNonnegative(width) ||
            !IsPOTAssumingNonnegative(height))
        {
            ErrorInvalidValue("%s: Width and height must be powers of two.",
                              InfoFrom(func, dims));
            return false;
        }
    }

    return true;
}

/**
 * Return true if the enough data is present to satisfy compressed
 * texture format constraints.
 */
bool
WebGLContext::ValidateCompTexImageDataSize(GLint level, GLenum format,
                                           GLsizei width, GLsizei height,
                                           uint32_t byteLength,
                                           WebGLTexImageFunc func,
                                           WebGLTexDimensions dims)
{
    // negative width and height must already have been handled above
    MOZ_ASSERT(width >= 0 && height >= 0);

    CheckedUint32 required_byteLength = 0;

    switch (format) {
    case LOCAL_GL_COMPRESSED_RGB_S3TC_DXT1_EXT:
    case LOCAL_GL_COMPRESSED_RGBA_S3TC_DXT1_EXT:
    case LOCAL_GL_ATC_RGB:
    case LOCAL_GL_ETC1_RGB8_OES:
        required_byteLength = ((CheckedUint32(width) + 3) / 4) *
                              ((CheckedUint32(height) + 3) / 4) * 8;
        break;

    case LOCAL_GL_COMPRESSED_RGBA_S3TC_DXT3_EXT:
    case LOCAL_GL_COMPRESSED_RGBA_S3TC_DXT5_EXT:
    case LOCAL_GL_ATC_RGBA_EXPLICIT_ALPHA:
    case LOCAL_GL_ATC_RGBA_INTERPOLATED_ALPHA:
        required_byteLength = ((CheckedUint32(width) + 3) / 4) *
                              ((CheckedUint32(height) + 3) / 4) * 16;
        break;

    case LOCAL_GL_COMPRESSED_RGB_PVRTC_4BPPV1:
    case LOCAL_GL_COMPRESSED_RGBA_PVRTC_4BPPV1:
        required_byteLength = CheckedUint32(std::max(width, 8)) *
                              CheckedUint32(std::max(height, 8)) / 2;
        break;

    case LOCAL_GL_COMPRESSED_RGB_PVRTC_2BPPV1:
    case LOCAL_GL_COMPRESSED_RGBA_PVRTC_2BPPV1:
        required_byteLength = CheckedUint32(std::max(width, 16)) *
                              CheckedUint32(std::max(height, 8)) / 4;
        break;
    }

    if (!required_byteLength.isValid() ||
        required_byteLength.value() != byteLength)
    {
        ErrorInvalidValue("%s: Data size does not match dimensions.",
                          InfoFrom(func, dims));
        return false;
    }

    return true;
}

/**
 * Validate the width, height, and depth of a texture image, \return
 * true is valid, false otherwise.
 * Used by all the (Compressed|Copy)?Tex(Sub)?Image functions.
 * Target and level must have been validated before calling.
 */
bool
WebGLContext::ValidateTexImageSize(TexImageTarget texImageTarget, GLint level,
                                   GLint width, GLint height, GLint depth,
                                   WebGLTexImageFunc func,
                                   WebGLTexDimensions dims)
{
    MOZ_ASSERT(level >= 0, "level should already be validated");

    /* Bug 966630: maxTextureSize >> level runs into "undefined"
     * behaviour depending on ISA. For example, on Intel shifts
     * amounts are mod 64 (in 64-bit mode on 64-bit dest) and mod 32
     * otherwise. This means 16384 >> 0x10000001 == 8192 which isn't
     * what would be expected. Make the required behaviour explicit by
     * clamping to a shift of 31 bits if level is greater than that
     * ammount. This will give 0 that if (!maxAllowedSize) is
     * expecting.
     */

    if (level > 31)
        level = 31;

    auto texTarget = TexImageTargetToTexTarget(texImageTarget);
    const GLuint maxTexImageSize = MaxTextureSizeForTarget(texTarget) >> level;

    const bool isCubemapTarget = IsTexImageCubemapTarget(texImageTarget.get());
    const bool isSub = IsSubFunc(func);

    if (!isSub && isCubemapTarget && (width != height)) {
        /* GL ES Version 2.0.25 - 3.7.1 Texture Image Specification
         *   "When the target parameter to TexImage2D is one of the
         *   six cube map two-dimensional image targets, the error
         *   INVALID_VALUE is generated if the width and height
         *   parameters are not equal."
         */
        ErrorInvalidValue("%s: For cube maps, width must equal height.",
                          InfoFrom(func, dims));
        return false;
    }

    if (texImageTarget == LOCAL_GL_TEXTURE_2D || isCubemapTarget) {
        /* GL ES Version 2.0.25 - 3.7.1 Texture Image Specification
         *   "If wt and ht are the specified image width and height,
         *   and if either wt or ht are less than zero, then the error
         *   INVALID_VALUE is generated."
         */
        if (width < 0) {
            ErrorInvalidValue("%s: Width must be >= 0.", InfoFrom(func, dims));
            return false;
        }

        if (height < 0) {
            ErrorInvalidValue("%s: Height must be >= 0.", InfoFrom(func, dims));
            return false;
        }

        /* GL ES Version 2.0.25 - 3.7.1 Texture Image Specification
         *   "The maximum allowable width and height of a
         *   two-dimensional texture image must be at least 2**(k−lod)
         *   for image arrays of level zero through k, where k is the
         *   log base 2 of MAX_TEXTURE_SIZE. and lod is the
         *   level-of-detail of the image array. It may be zero for
         *   image arrays of any level-of-detail greater than k. The
         *   error INVALID_VALUE is generated if the specified image
         *   is too large to be stored under any conditions.
         */
        if (width > (int) maxTexImageSize) {
            ErrorInvalidValue("%s: The maximum width for level %d is %u.",
                              InfoFrom(func, dims), level, maxTexImageSize);
            return false;
        }

        if (height > (int) maxTexImageSize) {
            ErrorInvalidValue("%s: The maximum height for level %d is %u.",
                              InfoFrom(func, dims), level, maxTexImageSize);
            return false;
        }

        /* GL ES Version 2.0.25 - 3.7.1 Texture Image Specification
         *   "If level is greater than zero, and either width or
         *   height is not a power-of-two, the error INVALID_VALUE is
         *   generated."
         *
         * This restriction does not apply to GL ES Version 3.0+.
         */
        if (!IsWebGL2() && level > 0) {
            if (!IsPOTAssumingNonnegative(width)) {
                ErrorInvalidValue("%s: For level > 0, width of %d must be a"
                                  " power of two.", InfoFrom(func, dims),
                                  width);
                return false;
            }

            if (!IsPOTAssumingNonnegative(height)) {
                ErrorInvalidValue("%s: For level > 0, height of %d must be a"
                                  " power of two.", InfoFrom(func, dims),
                                  height);
                return false;
            }
        }
    }

    // TODO: WebGL 2
    if (texImageTarget == LOCAL_GL_TEXTURE_3D) {
        if (depth < 0) {
            ErrorInvalidValue("%s: Depth must be >= 0.", InfoFrom(func, dims));
            return false;
        }

        if (!IsWebGL2() && !IsPOTAssumingNonnegative(depth)) {
            ErrorInvalidValue("%s: Depth of %d must be a power of two.",
                              InfoFrom(func, dims), depth);
            return false;
        }
    }

    return true;
}

/**
 * Validate texture image sizing for Tex(Sub)?Image variants.
 */
// TODO: WebGL 2. Update this to handle 3D textures.
bool
WebGLContext::ValidateTexSubImageSize(GLint xoffset, GLint yoffset,
                                      GLint /*zoffset*/, GLsizei width,
                                      GLsizei height, GLsizei /*depth*/,
                                      GLsizei baseWidth, GLsizei baseHeight,
                                      GLsizei /*baseDepth*/,
                                      WebGLTexImageFunc func,
                                      WebGLTexDimensions dims)
{
    /* GL ES Version 2.0.25 - 3.7.1 Texture Image Specification
     *   "Taking wt and ht to be the specified width and height of the
     *   texture array, and taking x, y, w, and h to be the xoffset,
     *   yoffset, width, and height argument values, any of the
     *   following relationships generates the error INVALID_VALUE:
     *       x < 0
     *       x + w > wt
     *       y < 0
     *       y + h > ht"
     */

    if (xoffset < 0) {
        ErrorInvalidValue("%s: xoffset must be >= 0.", InfoFrom(func, dims));
        return false;
    }

    if (yoffset < 0) {
        ErrorInvalidValue("%s: yoffset must be >= 0.", InfoFrom(func, dims));
        return false;
    }

    if (!CanvasUtils::CheckSaneSubrectSize(xoffset, yoffset, width, height,
                                           baseWidth, baseHeight))
    {
        ErrorInvalidValue("%s: Subtexture rectangle out-of-bounds.",
                          InfoFrom(func, dims));
        return false;
    }

    return true;
}

/**
 * Perform validation of format/type combinations for TexImage variants.
 * Returns true if the format/type is a valid combination, false otherwise.
 */
bool
WebGLContext::ValidateTexImageFormatAndType(GLenum format, GLenum type,
                                            WebGLTexImageFunc func,
                                            WebGLTexDimensions dims)
{
    if (IsCompressedFunc(func) || IsCopyFunc(func)) {
        MOZ_ASSERT(type == LOCAL_GL_NONE && format == LOCAL_GL_NONE);
        return true;
    }

    if (!ValidateTexImageFormat(format, func, dims) ||
        !ValidateTexImageType(type, func, dims))
    {
        return false;
    }

    // Here we're reinterpreting format as an unsized internalformat;
    // these are the same in practice and there's no point in having the
    // same code implemented twice.
    TexInternalFormat effective =
        EffectiveInternalFormatFromInternalFormatAndType(format, type);

    if (effective != LOCAL_GL_NONE)
        return true;

    ErrorInvalidOperation("%s: Invalid combination of format %s and type %s.",
                          InfoFrom(func, dims), WebGLContext::EnumName(format),
                          WebGLContext::EnumName(type));
    return false;
}

bool
WebGLContext::ValidateCompTexImageInternalFormat(GLenum format,
                                                 WebGLTexImageFunc func,
                                                 WebGLTexDimensions dims)
{
    if (!IsCompressedTextureFormat(format)) {
        ErrorInvalidEnum("%s: Invalid compressed texture format: %s",
                         InfoFrom(func, dims), WebGLContext::EnumName(format));
        return false;
    }

    /* WEBGL_compressed_texture_atc added formats */
    if (format == LOCAL_GL_ATC_RGB ||
        format == LOCAL_GL_ATC_RGBA_EXPLICIT_ALPHA ||
        format == LOCAL_GL_ATC_RGBA_INTERPOLATED_ALPHA)
    {
        if (IsExtensionEnabled(WebGLExtensionID::WEBGL_compressed_texture_atc))
            return true;

        ErrorInvalidEnum("%s: Invalid format %s: Requires that"
                         " WEBGL_compressed_texture_atc is enabled.",
                         InfoFrom(func, dims), WebGLContext::EnumName(format));
        return false;
    }

    // WEBGL_compressed_texture_etc1
    if (format == LOCAL_GL_ETC1_RGB8_OES) {
        if (IsExtensionEnabled(WebGLExtensionID::WEBGL_compressed_texture_etc1))
            return true;

        ErrorInvalidEnum("%s: Invalid format %s: Requires that"
                         " WEBGL_compressed_texture_etc1 is enabled.",
                         InfoFrom(func, dims), WebGLContext::EnumName(format));
        return false;
    }


    if (format == LOCAL_GL_COMPRESSED_RGB_PVRTC_2BPPV1 ||
        format == LOCAL_GL_COMPRESSED_RGB_PVRTC_4BPPV1 ||
        format == LOCAL_GL_COMPRESSED_RGBA_PVRTC_2BPPV1 ||
        format == LOCAL_GL_COMPRESSED_RGBA_PVRTC_4BPPV1)
    {
        if (IsExtensionEnabled(WebGLExtensionID::WEBGL_compressed_texture_pvrtc))
            return true;

        ErrorInvalidEnum("%s: Invalid format %s: Requires that"
                         " WEBGL_compressed_texture_pvrtc is enabled.",
                         InfoFrom(func, dims), WebGLContext::EnumName(format));
        return false;
    }


    if (format == LOCAL_GL_COMPRESSED_RGB_S3TC_DXT1_EXT ||
        format == LOCAL_GL_COMPRESSED_RGBA_S3TC_DXT1_EXT ||
        format == LOCAL_GL_COMPRESSED_RGBA_S3TC_DXT3_EXT ||
        format == LOCAL_GL_COMPRESSED_RGBA_S3TC_DXT5_EXT)
    {
        if (IsExtensionEnabled(WebGLExtensionID::WEBGL_compressed_texture_s3tc))
            return true;

        ErrorInvalidEnum("%s: Invalid format %s: Requires that"
                         " WEBGL_compressed_texture_s3tc is enabled.",
                         InfoFrom(func, dims), WebGLContext::EnumName(format));
        return false;
    }

    MOZ_ASSERT(false);
    return false;
}

bool
WebGLContext::ValidateCopyTexImageInternalFormat(GLenum format,
                                                 WebGLTexImageFunc func,
                                                 WebGLTexDimensions dims)
{
    switch (format) {
    case LOCAL_GL_RGBA:
    case LOCAL_GL_RGB:
    case LOCAL_GL_LUMINANCE_ALPHA:
    case LOCAL_GL_LUMINANCE:
    case LOCAL_GL_ALPHA:
        return true;
    }
    // In CopyTexImage, internalFormat is a function parameter,
    // so a bad value is an INVALID_ENUM error.
    // In CopyTexSubImage, internalFormat is part of existing state,
    // so this is an INVALID_OPERATION error.
    GenerateWarning("%s: Invalid texture internal format: %s",
                    InfoFrom(func, dims), WebGLContext::EnumName(format));

    GLenum error;
    if (func == WebGLTexImageFunc::CopyTexImage)
        error = LOCAL_GL_INVALID_ENUM;
    else
        error = LOCAL_GL_INVALID_OPERATION;

    SynthesizeGLError(error);
    return false;
}
/**
 * Return true if format, type and jsArrayType are a valid combination.
 * Also returns the size for texel of format and type (in bytes) via
 * \a texelSize.
 *
 * It is assumed that type has previously been validated.
 */
bool
WebGLContext::ValidateTexInputData(GLenum type, js::Scalar::Type jsArrayType,
                                   WebGLTexImageFunc func,
                                   WebGLTexDimensions dims)
{
    // We're using js::Scalar::MaxTypedArrayViewType as dummy value for when
    // the tex source wasn't a typed array.
    if (jsArrayType == js::Scalar::MaxTypedArrayViewType)
        return true;

    const char invalidTypedArray[] = "%s: Invalid typed array type for given"
                                     " texture data type.";

    bool validInput = false;
    switch (type) {
    case LOCAL_GL_UNSIGNED_BYTE:
        validInput = jsArrayType == js::Scalar::Uint8;
        break;

    case LOCAL_GL_BYTE:
        validInput = jsArrayType == js::Scalar::Int8;
        break;

    case LOCAL_GL_HALF_FLOAT:
    case LOCAL_GL_UNSIGNED_SHORT:
    case LOCAL_GL_UNSIGNED_SHORT_4_4_4_4:
    case LOCAL_GL_UNSIGNED_SHORT_5_5_5_1:
    case LOCAL_GL_UNSIGNED_SHORT_5_6_5:
        validInput = jsArrayType == js::Scalar::Uint16;
        break;

    case LOCAL_GL_SHORT:
        validInput = jsArrayType == js::Scalar::Int16;
        break;

    case LOCAL_GL_UNSIGNED_INT:
    case LOCAL_GL_UNSIGNED_INT_24_8:
    case LOCAL_GL_UNSIGNED_INT_2_10_10_10_REV:
    case LOCAL_GL_UNSIGNED_INT_10F_11F_11F_REV:
    case LOCAL_GL_UNSIGNED_INT_5_9_9_9_REV:
        validInput = jsArrayType == js::Scalar::Uint32;
        break;

    case LOCAL_GL_INT:
        validInput = jsArrayType == js::Scalar::Int32;
        break;

    case LOCAL_GL_FLOAT:
        validInput = jsArrayType == js::Scalar::Float32;
        break;

    default:
        break;
    }

    if (!validInput)
        ErrorInvalidOperation(invalidTypedArray, InfoFrom(func, dims));

    return validInput;
}

/**
 * Checks specific for the CopyTex[Sub]Image2D functions.
 * Verifies:
 * - Framebuffer is complete and has valid read planes
 * - Copy format is a subset of framebuffer format (i.e. all required components
 *   are available)
 */
bool
WebGLContext::ValidateCopyTexImage(GLenum format, WebGLTexImageFunc func,
                                   WebGLTexDimensions dims)
{
    MOZ_ASSERT(IsCopyFunc(func));

    // Default framebuffer format
    GLenum fboFormat = mOptions.alpha ? LOCAL_GL_RGBA : LOCAL_GL_RGB;

    if (mBoundFramebuffer) {
        if (!mBoundFramebuffer->CheckAndInitializeAttachments()) {
            ErrorInvalidFramebufferOperation("%s: Incomplete framebuffer.",
                                             InfoFrom(func, dims));
            return false;
        }

        GLenum readPlaneBits = LOCAL_GL_COLOR_BUFFER_BIT;
        if (!mBoundFramebuffer->HasCompletePlanes(readPlaneBits)) {
            ErrorInvalidOperation("%s: Read source attachment doesn't have the"
                                  " correct color/depth/stencil type.",
                                  InfoFrom(func, dims));
            return false;
        }

        // Get the correct format for the framebuffer, as it's not the default
        // one.
        const WebGLFramebuffer::Attachment& color0 =
            mBoundFramebuffer->GetAttachment(LOCAL_GL_COLOR_ATTACHMENT0);

        fboFormat = mBoundFramebuffer->GetFormatForAttachment(color0);
    }

    // Make sure the format of the framebuffer is a superset of the format
    // requested by the CopyTex[Sub]Image2D functions.
    const GLComponents formatComps = GLComponents(format);
    const GLComponents fboComps = GLComponents(fboFormat);
    if (!formatComps.IsSubsetOf(fboComps)) {
        ErrorInvalidOperation("%s: Format %s is not a subset of the current"
                              " framebuffer format, which is %s.",
                              InfoFrom(func, dims), EnumName(format),
                              EnumName(fboFormat));
        return false;
    }

    return true;
}

/**
 * Test the gl(Copy|Compressed)?Tex[Sub]?Image[23]() parameters for errors.
 * Verifies each of the parameters against the WebGL standard and enabled
 * extensions.
 */
// TODO: Texture dims is here for future expansion in WebGL 2.0
bool
WebGLContext::ValidateTexImage(TexImageTarget texImageTarget, GLint level,
                               GLenum internalFormat, GLint xoffset,
                               GLint yoffset, GLint zoffset, GLint width,
                               GLint height, GLint depth, GLint border,
                               GLenum format, GLenum type,
                               WebGLTexImageFunc func,
                               WebGLTexDimensions dims)
{
    const char* info = InfoFrom(func, dims);

    // Check level
    if (level < 0) {
        ErrorInvalidValue("%s: `level` must be >= 0.", info);
        return false;
    }

    // Check border
    if (border != 0) {
        ErrorInvalidValue("%s: `border` must be 0.", info);
        return false;
    }

    // Check incoming image format and type
    if (!ValidateTexImageFormatAndType(format, type, func, dims))
        return false;

    if (!TexInternalFormat::IsValueLegal(internalFormat)) {
        ErrorInvalidEnum("%s: Invalid `internalformat` enum %s.", info,
                         EnumName(internalFormat));
        return false;
    }
    TexInternalFormat unsizedInternalFormat =
        UnsizedInternalFormatFromInternalFormat(internalFormat);

    if (IsCompressedFunc(func)) {
        if (!ValidateCompTexImageInternalFormat(internalFormat, func, dims))
            return false;

    } else if (IsCopyFunc(func)) {
        if (!ValidateCopyTexImageInternalFormat(unsizedInternalFormat.get(),
                                                func, dims))
        {
            return false;
        }
    } else if (format != unsizedInternalFormat) {
        if (IsWebGL2()) {
            // In WebGL2, it's OK to have `internalFormat != format` if
            // internalFormat is the sized internal format corresponding to the
            // (format, type) pair according to Table 3.2 in the OpenGL ES 3.0.3
            // spec.
            auto effectiveFormat = EffectiveInternalFormatFromInternalFormatAndType(format,
                                                                                    type);
            if (internalFormat != effectiveFormat) {
                bool exceptionallyAllowed = false;
                if (internalFormat == LOCAL_GL_SRGB8_ALPHA8 &&
                    format == LOCAL_GL_RGBA &&
                    type == LOCAL_GL_UNSIGNED_BYTE)
                {
                    exceptionallyAllowed = true;
                }
                else if (internalFormat == LOCAL_GL_SRGB8 &&
                         format == LOCAL_GL_RGB &&
                         type == LOCAL_GL_UNSIGNED_BYTE)
                {
                    exceptionallyAllowed = true;
                }
                if (!exceptionallyAllowed) {
                    ErrorInvalidOperation("%s: `internalformat` does not match"
                                          " `format` and `type`.", info);
                    return false;
                }
            }
        } else {
            // In WebGL 1, format must be equal to internalformat.
            ErrorInvalidOperation("%s: `internalformat` does not match"
                                  " `format`.", info);
            return false;
        }
    }

    // Check texture image size
    if (!ValidateTexImageSize(texImageTarget, level, width, height, 0, func,
                              dims))
    {
        return false;
    }

    /* 5.14.8 Texture objects - WebGL Spec.
     *   "If an attempt is made to call these functions with no
     *    WebGLTexture bound (see above), an INVALID_OPERATION error
     *    is generated."
     */
    WebGLTexture* tex = ActiveBoundTextureForTexImageTarget(texImageTarget);
    if (!tex) {
        ErrorInvalidOperation("%s: No texture is bound to target %s.", info,
                              WebGLContext::EnumName(texImageTarget.get()));
        return false;
    }

    if (IsSubFunc(func)) {
        if (!tex->HasImageInfoAt(texImageTarget, level)) {
            ErrorInvalidOperation("%s: No texture image previously defined for"
                                  " target %s at level %d.", info,
                                  WebGLContext::EnumName(texImageTarget.get()),
                                                         level);
            return false;
        }

        const auto& imageInfo = tex->ImageInfoAt(texImageTarget, level);
        if (!ValidateTexSubImageSize(xoffset, yoffset, zoffset, width, height,
                                     depth, imageInfo.Width(),
                                     imageInfo.Height(), 0, func, dims))
        {
            return false;
        }
    }

    // Additional checks for depth textures
    if (texImageTarget != LOCAL_GL_TEXTURE_2D &&
        (format == LOCAL_GL_DEPTH_COMPONENT ||
         format == LOCAL_GL_DEPTH_STENCIL))
    {
        ErrorInvalidOperation("%s: With format of %s, target must be"
                              " TEXTURE_2D.", info,
                              WebGLContext::EnumName(format));
        return false;
    }

    // Additional checks for compressed textures
    if (!IsAllowedFromSource(internalFormat, func)) {
        ErrorInvalidOperation("%s: Invalid format %s for this operation.",
                              info, WebGLContext::EnumName(format));
        return false;
    }

    // Parameters are OK
    return true;
}

bool
WebGLContext::ValidateUniformLocation(const char* info,
                                      WebGLUniformLocation* loc)
{
    if (!ValidateObjectAllowNull(info, loc))
        return false;

    if (!loc)
        return false;

    // The need to check specifically for !mCurrentProgram here is explained in
    // bug 657556.
    if (!mCurrentProgram) {
        ErrorInvalidOperation("%s: No program is currently bound.", info);
        return false;
    }

    if (mCurrentProgram != loc->Program()) {
        ErrorInvalidOperation("%s: This uniform location doesn't correspond to"
                              " the current program.", info);
        return false;
    }

    if (mCurrentProgram->Generation() != loc->ProgramGeneration()) {
        ErrorInvalidOperation("%s: This uniform location is obsolete since the"
                              " program has been relinked.", info);
        return false;
    }

    return true;
}

bool
WebGLContext::ValidateSamplerUniformSetter(const char* info,
                                           WebGLUniformLocation* loc,
                                           GLint value)
{
    if (loc->Info().type != LOCAL_GL_SAMPLER_2D &&
        loc->Info().type != LOCAL_GL_SAMPLER_CUBE)
    {
        return true;
    }

    if (value >= 0 && value < mGLMaxTextureUnits)
        return true;

    ErrorInvalidValue("%s: This uniform location is a sampler, but %d is not a"
                      " valid texture unit.", info, value);
    return false;
}

bool
WebGLContext::ValidateAttribArraySetter(const char* name, uint32_t cnt,
                                        uint32_t arrayLength)
{
    if (IsContextLost())
        return false;

    if (arrayLength < cnt) {
        ErrorInvalidOperation("%s: Array must be >= %d elements.", name, cnt);
        return false;
    }

    return true;
}

static bool
IsUniformSetterTypeValid(GLenum setterType, GLenum uniformType)
{
    switch (uniformType) {
    case LOCAL_GL_BOOL:
    case LOCAL_GL_BOOL_VEC2:
    case LOCAL_GL_BOOL_VEC3:
    case LOCAL_GL_BOOL_VEC4:
        return true; // GLfloat(0.0) sets a bool to false.

    case LOCAL_GL_INT:
    case LOCAL_GL_INT_SAMPLER_2D:
    case LOCAL_GL_INT_SAMPLER_2D_ARRAY:
    case LOCAL_GL_INT_SAMPLER_3D:
    case LOCAL_GL_INT_SAMPLER_CUBE:
    case LOCAL_GL_INT_VEC2:
    case LOCAL_GL_INT_VEC3:
    case LOCAL_GL_INT_VEC4:
    case LOCAL_GL_SAMPLER_2D:
    case LOCAL_GL_SAMPLER_2D_ARRAY:
    case LOCAL_GL_SAMPLER_2D_ARRAY_SHADOW:
    case LOCAL_GL_SAMPLER_2D_SHADOW:
    case LOCAL_GL_SAMPLER_CUBE:
    case LOCAL_GL_SAMPLER_CUBE_SHADOW:
    case LOCAL_GL_UNSIGNED_INT_SAMPLER_2D:
    case LOCAL_GL_UNSIGNED_INT_SAMPLER_2D_ARRAY:
    case LOCAL_GL_UNSIGNED_INT_SAMPLER_3D:
    case LOCAL_GL_UNSIGNED_INT_SAMPLER_CUBE:
        return setterType == LOCAL_GL_INT;

    case LOCAL_GL_FLOAT:
    case LOCAL_GL_FLOAT_MAT2:
    case LOCAL_GL_FLOAT_MAT2x3:
    case LOCAL_GL_FLOAT_MAT2x4:
    case LOCAL_GL_FLOAT_MAT3:
    case LOCAL_GL_FLOAT_MAT3x2:
    case LOCAL_GL_FLOAT_MAT3x4:
    case LOCAL_GL_FLOAT_MAT4:
    case LOCAL_GL_FLOAT_MAT4x2:
    case LOCAL_GL_FLOAT_MAT4x3:
    case LOCAL_GL_FLOAT_VEC2:
    case LOCAL_GL_FLOAT_VEC3:
    case LOCAL_GL_FLOAT_VEC4:
        return setterType == LOCAL_GL_FLOAT;

    default:
        MOZ_ASSERT(false); // should never get here
        return false;
    }
}

static bool
CheckUniformSizeAndType(WebGLContext& webgl, WebGLUniformLocation* loc,
                        uint8_t setterElemSize, GLenum setterType,
                        const char* info)
{
    if (setterElemSize != loc->ElementSize()) {
        webgl.ErrorInvalidOperation("%s: Bad uniform size: %i", info,
                                    loc->ElementSize());
        return false;
    }

    if (!IsUniformSetterTypeValid(setterType, loc->Info().type)) {
        webgl.ErrorInvalidOperation("%s: Bad uniform type: %i", info,
                                    loc->Info().type);
        return false;
    }

    return true;
}

static bool
CheckUniformArrayLength(WebGLContext& webgl, WebGLUniformLocation* loc,
                        uint8_t setterElemSize, size_t setterArraySize,
                        const char* info)
{
    if (setterArraySize == 0 ||
        setterArraySize % setterElemSize)
    {
        webgl.ErrorInvalidValue("%s: expected an array of length a multiple of"
                                " %d, got an array of length %d.", info,
                                setterElemSize, setterArraySize);
        return false;
    }

    if (!loc->Info().isArray &&
        setterArraySize != setterElemSize)
    {
        webgl.ErrorInvalidOperation("%s: expected an array of length exactly %d"
                                    " (since this uniform is not an array"
                                    " uniform), got an array of length %d.",
                                    info, setterElemSize, setterArraySize);
        return false;
    }

    return true;
}

bool
WebGLContext::ValidateUniformSetter(WebGLUniformLocation* loc,
                                    uint8_t setterElemSize, GLenum setterType,
                                    const char* info, GLuint* out_rawLoc)
{
    if (IsContextLost())
        return false;

    if (!ValidateUniformLocation(info, loc))
        return false;

    if (!CheckUniformSizeAndType(*this, loc, setterElemSize, setterType, info))
        return false;

    *out_rawLoc = loc->Location();
    return true;
}

bool
WebGLContext::ValidateUniformArraySetter(WebGLUniformLocation* loc,
                                         uint8_t setterElemSize,
                                         GLenum setterType,
                                         size_t setterArraySize,
                                         const char* info,
                                         GLuint* const out_rawLoc,
                                         GLsizei* const out_numElementsToUpload)
{
    if (IsContextLost())
        return false;

    if (!ValidateUniformLocation(info, loc))
        return false;

    if (!CheckUniformSizeAndType(*this, loc, setterElemSize, setterType, info))
        return false;

    if (!CheckUniformArrayLength(*this, loc, setterElemSize, setterArraySize,
                                 info))
    {
        return false;
    }

    *out_rawLoc = loc->Location();
    *out_numElementsToUpload = std::min((size_t)loc->Info().arraySize,
                                        setterArraySize / setterElemSize);
    return true;
}

bool
WebGLContext::ValidateUniformMatrixArraySetter(WebGLUniformLocation* loc,
                                               uint8_t setterDims,
                                               GLenum setterType,
                                               size_t setterArraySize,
                                               bool setterTranspose,
                                               const char* info,
                                               GLuint* const out_rawLoc,
                                               GLsizei* const out_numElementsToUpload)
{
    uint8_t setterElemSize = setterDims * setterDims;

    if (IsContextLost())
        return false;

    if (!ValidateUniformLocation(info, loc))
        return false;

    if (!CheckUniformSizeAndType(*this, loc, setterElemSize, setterType, info))
        return false;

    if (!CheckUniformArrayLength(*this, loc, setterElemSize, setterArraySize,
                                 info))
    {
        return false;
    }

    if (setterTranspose) {
        ErrorInvalidValue("%s: `transpose` must be false.", info);
        return false;
    }

    *out_rawLoc = loc->Location();
    *out_numElementsToUpload = std::min((size_t)loc->Info().arraySize,
                                        setterArraySize / setterElemSize);
    return true;
}

bool
WebGLContext::ValidateAttribIndex(GLuint index, const char* info)
{
    bool valid = (index < MaxVertexAttribs());

    if (!valid) {
        if (index == GLuint(-1)) {
            ErrorInvalidValue("%s: -1 is not a valid `index`. This value"
                              " probably comes from a getAttribLocation()"
                              " call, where this return value -1 means"
                              " that the passed name didn't correspond to"
                              " an active attribute in the specified"
                              " program.", info);
        } else {
            ErrorInvalidValue("%s: `index` must be less than"
                              " MAX_VERTEX_ATTRIBS.", info);
        }
    }

    return valid;
}

bool
WebGLContext::ValidateAttribPointer(bool integerMode, GLuint index, GLint size, GLenum type,
                                    WebGLboolean normalized, GLsizei stride,
                                    WebGLintptr byteOffset, const char* info)
{
    WebGLBuffer* buffer = mBoundArrayBuffer;
    if (!buffer) {
        ErrorInvalidOperation("%s: must have valid GL_ARRAY_BUFFER binding", info);
        return false;
    }

    GLsizei requiredAlignment = 0;
    if (!ValidateAttribPointerType(integerMode, type, &requiredAlignment, info))
        return false;

    // requiredAlignment should always be a power of two
    MOZ_ASSERT(IsPOTAssumingNonnegative(requiredAlignment));
    GLsizei requiredAlignmentMask = requiredAlignment - 1;

    if (size < 1 || size > 4) {
        ErrorInvalidValue("%s: invalid element size", info);
        return false;
    }

    // see WebGL spec section 6.6 "Vertex Attribute Data Stride"
    if (stride < 0 || stride > 255) {
        ErrorInvalidValue("%s: negative or too large stride", info);
        return false;
    }

    if (byteOffset < 0) {
        ErrorInvalidValue("%s: negative offset", info);
        return false;
    }

    if (stride & requiredAlignmentMask) {
        ErrorInvalidOperation("%s: stride doesn't satisfy the alignment "
                              "requirement of given type", info);
        return false;
    }

    if (byteOffset & requiredAlignmentMask) {
        ErrorInvalidOperation("%s: byteOffset doesn't satisfy the alignment "
                              "requirement of given type", info);
        return false;
    }

    return true;
}

bool
WebGLContext::ValidateStencilParamsForDrawCall()
{
    const char msg[] = "%s set different front and back stencil %s. Drawing in"
                       " this configuration is not allowed.";

    if (mStencilRefFront != mStencilRefBack) {
        ErrorInvalidOperation(msg, "stencilFuncSeparate", "reference values");
        return false;
    }

    if (mStencilValueMaskFront != mStencilValueMaskBack) {
        ErrorInvalidOperation(msg, "stencilFuncSeparate", "value masks");
        return false;
    }

    if (mStencilWriteMaskFront != mStencilWriteMaskBack) {
        ErrorInvalidOperation(msg, "stencilMaskSeparate", "write masks");
        return false;
    }

    return true;
}

static inline int32_t
FloorPOT(int32_t x)
{
    MOZ_ASSERT(x > 0);
    int32_t pot = 1;
    while (pot < 0x40000000) {
        if (x < pot*2)
            break;
        pot *= 2;
    }
    return pot;
}

bool
WebGLContext::InitAndValidateGL()
{
    if (!gl)
        return false;

    GLenum error = gl->fGetError();
    if (error != LOCAL_GL_NO_ERROR) {
        GenerateWarning("GL error 0x%x occurred during OpenGL context"
                        " initialization, before WebGL initialization!", error);
        return false;
    }

    mMinCapability = Preferences::GetBool("webgl.min_capability_mode", false);
    mDisableExtensions = Preferences::GetBool("webgl.disable-extensions", false);
    mLoseContextOnMemoryPressure = Preferences::GetBool("webgl.lose-context-on-memory-pressure", false);
    mCanLoseContextInForeground = Preferences::GetBool("webgl.can-lose-context-in-foreground", true);
    mRestoreWhenVisible = Preferences::GetBool("webgl.restore-context-when-visible", true);

    if (MinCapabilityMode())
        mDisableFragHighP = true;

    // These are the default values, see 6.2 State tables in the
    // OpenGL ES 2.0.25 spec.
    mColorWriteMask[0] = 1;
    mColorWriteMask[1] = 1;
    mColorWriteMask[2] = 1;
    mColorWriteMask[3] = 1;
    mDepthWriteMask = 1;
    mColorClearValue[0] = 0.f;
    mColorClearValue[1] = 0.f;
    mColorClearValue[2] = 0.f;
    mColorClearValue[3] = 0.f;
    mDepthClearValue = 1.f;
    mStencilClearValue = 0;
    mStencilRefFront = 0;
    mStencilRefBack = 0;

    /*
    // Technically, we should be setting mStencil[...] values to
    // `allOnes`, but either ANGLE breaks or the SGX540s on Try break.
    GLuint stencilBits = 0;
    gl->GetUIntegerv(LOCAL_GL_STENCIL_BITS, &stencilBits);
    GLuint allOnes = ~(UINT32_MAX << stencilBits);
    mStencilValueMaskFront = allOnes;
    mStencilValueMaskBack  = allOnes;
    mStencilWriteMaskFront = allOnes;
    mStencilWriteMaskBack  = allOnes;
    */

    gl->GetUIntegerv(LOCAL_GL_STENCIL_VALUE_MASK,      &mStencilValueMaskFront);
    gl->GetUIntegerv(LOCAL_GL_STENCIL_BACK_VALUE_MASK, &mStencilValueMaskBack);
    gl->GetUIntegerv(LOCAL_GL_STENCIL_WRITEMASK,       &mStencilWriteMaskFront);
    gl->GetUIntegerv(LOCAL_GL_STENCIL_BACK_WRITEMASK,  &mStencilWriteMaskBack);

    AssertUintParamCorrect(gl, LOCAL_GL_STENCIL_VALUE_MASK,      mStencilValueMaskFront);
    AssertUintParamCorrect(gl, LOCAL_GL_STENCIL_BACK_VALUE_MASK, mStencilValueMaskBack);
    AssertUintParamCorrect(gl, LOCAL_GL_STENCIL_WRITEMASK,       mStencilWriteMaskFront);
    AssertUintParamCorrect(gl, LOCAL_GL_STENCIL_BACK_WRITEMASK,  mStencilWriteMaskBack);

    mDitherEnabled = true;
    mRasterizerDiscardEnabled = false;
    mScissorTestEnabled = false;

    // Bindings, etc.
    mActiveTexture = 0;
    mEmitContextLostErrorOnce = true;
    mWebGLError = LOCAL_GL_NO_ERROR;
    mUnderlyingGLError = LOCAL_GL_NO_ERROR;

    mBound2DTextures.Clear();
    mBoundCubeMapTextures.Clear();
    mBound3DTextures.Clear();

    mBoundArrayBuffer = nullptr;
    mBoundTransformFeedbackBuffer = nullptr;
    mCurrentProgram = nullptr;

    mBoundFramebuffer = nullptr;
    mBoundRenderbuffer = nullptr;

    MakeContextCurrent();

    // on desktop OpenGL, we always keep vertex attrib 0 array enabled
    if (!gl->IsGLES())
        gl->fEnableVertexAttribArray(0);

    if (MinCapabilityMode())
        mGLMaxVertexAttribs = MINVALUE_GL_MAX_VERTEX_ATTRIBS;
    else
        gl->fGetIntegerv(LOCAL_GL_MAX_VERTEX_ATTRIBS, &mGLMaxVertexAttribs);

    if (mGLMaxVertexAttribs < 8) {
        GenerateWarning("GL_MAX_VERTEX_ATTRIBS: %d is < 8!",
                        mGLMaxVertexAttribs);
        return false;
    }

    // Note: GL_MAX_TEXTURE_UNITS is fixed at 4 for most desktop hardware,
    // even though the hardware supports much more.  The
    // GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS value is the accurate value.
    if (MinCapabilityMode())
        mGLMaxTextureUnits = MINVALUE_GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS;
    else
        gl->fGetIntegerv(LOCAL_GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, &mGLMaxTextureUnits);

    if (mGLMaxTextureUnits < 8) {
        GenerateWarning("GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS: %d is < 8!",
                        mGLMaxTextureUnits);
        return false;
    }

    mBound2DTextures.SetLength(mGLMaxTextureUnits);
    mBoundCubeMapTextures.SetLength(mGLMaxTextureUnits);
    mBound3DTextures.SetLength(mGLMaxTextureUnits);

    if (MinCapabilityMode()) {
        mGLMaxTextureSize = MINVALUE_GL_MAX_TEXTURE_SIZE;
        mGLMaxCubeMapTextureSize = MINVALUE_GL_MAX_CUBE_MAP_TEXTURE_SIZE;
        mGLMaxRenderbufferSize = MINVALUE_GL_MAX_RENDERBUFFER_SIZE;
        mGLMaxTextureImageUnits = MINVALUE_GL_MAX_TEXTURE_IMAGE_UNITS;
        mGLMaxVertexTextureImageUnits = MINVALUE_GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS;
    } else {
        gl->fGetIntegerv(LOCAL_GL_MAX_TEXTURE_SIZE, &mGLMaxTextureSize);
        gl->fGetIntegerv(LOCAL_GL_MAX_CUBE_MAP_TEXTURE_SIZE, &mGLMaxCubeMapTextureSize);
        gl->fGetIntegerv(LOCAL_GL_MAX_RENDERBUFFER_SIZE, &mGLMaxRenderbufferSize);
        gl->fGetIntegerv(LOCAL_GL_MAX_TEXTURE_IMAGE_UNITS, &mGLMaxTextureImageUnits);
        gl->fGetIntegerv(LOCAL_GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS, &mGLMaxVertexTextureImageUnits);
    }

    // Calculate log2 of mGLMaxTextureSize and mGLMaxCubeMapTextureSize
    mGLMaxTextureSizeLog2 = 0;
    int32_t tempSize = mGLMaxTextureSize;
    while (tempSize >>= 1) {
        ++mGLMaxTextureSizeLog2;
    }

    mGLMaxCubeMapTextureSizeLog2 = 0;
    tempSize = mGLMaxCubeMapTextureSize;
    while (tempSize >>= 1) {
        ++mGLMaxCubeMapTextureSizeLog2;
    }

    mGLMaxTextureSize = FloorPOT(mGLMaxTextureSize);
    mGLMaxRenderbufferSize = FloorPOT(mGLMaxRenderbufferSize);

    if (MinCapabilityMode()) {
        mGLMaxFragmentUniformVectors = MINVALUE_GL_MAX_FRAGMENT_UNIFORM_VECTORS;
        mGLMaxVertexUniformVectors = MINVALUE_GL_MAX_VERTEX_UNIFORM_VECTORS;
        mGLMaxVaryingVectors = MINVALUE_GL_MAX_VARYING_VECTORS;
    } else {
        if (gl->IsSupported(gl::GLFeature::ES2_compatibility)) {
            gl->fGetIntegerv(LOCAL_GL_MAX_FRAGMENT_UNIFORM_VECTORS, &mGLMaxFragmentUniformVectors);
            gl->fGetIntegerv(LOCAL_GL_MAX_VERTEX_UNIFORM_VECTORS, &mGLMaxVertexUniformVectors);
            gl->fGetIntegerv(LOCAL_GL_MAX_VARYING_VECTORS, &mGLMaxVaryingVectors);
        } else {
            gl->fGetIntegerv(LOCAL_GL_MAX_FRAGMENT_UNIFORM_COMPONENTS, &mGLMaxFragmentUniformVectors);
            mGLMaxFragmentUniformVectors /= 4;
            gl->fGetIntegerv(LOCAL_GL_MAX_VERTEX_UNIFORM_COMPONENTS, &mGLMaxVertexUniformVectors);
            mGLMaxVertexUniformVectors /= 4;

            /* We are now going to try to read GL_MAX_VERTEX_OUTPUT_COMPONENTS
             * and GL_MAX_FRAGMENT_INPUT_COMPONENTS, however these constants
             * only entered the OpenGL standard at OpenGL 3.2. So we will try
             * reading, and check OpenGL error for INVALID_ENUM.
             *
             * On the public_webgl list, "problematic GetParameter pnames"
             * thread, the following formula was given:
             *   maxVaryingVectors = min(GL_MAX_VERTEX_OUTPUT_COMPONENTS,
             *                           GL_MAX_FRAGMENT_INPUT_COMPONENTS) / 4
             */
            GLint maxVertexOutputComponents = 0;
            GLint maxFragmentInputComponents = 0;

            const bool ok = (gl->GetPotentialInteger(LOCAL_GL_MAX_VERTEX_OUTPUT_COMPONENTS,
                                                     &maxVertexOutputComponents) &&
                             gl->GetPotentialInteger(LOCAL_GL_MAX_FRAGMENT_INPUT_COMPONENTS,
                                                     &maxFragmentInputComponents));

            if (ok) {
                mGLMaxVaryingVectors = std::min(maxVertexOutputComponents,
                                                maxFragmentInputComponents) / 4;
            } else {
                mGLMaxVaryingVectors = 16;
                // 16 = 64/4, and 64 is the min value for
                // maxVertexOutputComponents in the OpenGL 3.2 spec.
            }
        }
    }

    // Always 1 for GLES2
    mMaxFramebufferColorAttachments = 1;

    if (!gl->IsGLES()) {
        // gl_PointSize is always available in ES2 GLSL, but has to be
        // specifically enabled on desktop GLSL.
        gl->fEnable(LOCAL_GL_VERTEX_PROGRAM_POINT_SIZE);

        /* gl_PointCoord is always available in ES2 GLSL and in newer desktop
         * GLSL versions, but apparently not in OpenGL 2 and apparently not (due
         * to a driver bug) on certain NVIDIA setups. See:
         *   http://www.opengl.org/discussion_boards/ubbthreads.php?ubb=showflat&Number=261472
         *
         * Note that this used to cause crashes on old ATI drivers... Hopefully
         * not a significant anymore. See bug 602183.
         */
        gl->fEnable(LOCAL_GL_POINT_SPRITE);
    }

#ifdef XP_MACOSX
    if (gl->WorkAroundDriverBugs() &&
        gl->Vendor() == gl::GLVendor::ATI &&
        !nsCocoaFeatures::IsAtLeastVersion(10,9))
    {
        // The Mac ATI driver, in all known OSX version up to and including
        // 10.8, renders points sprites upside-down. (Apple bug 11778921)
        gl->fPointParameterf(LOCAL_GL_POINT_SPRITE_COORD_ORIGIN,
                             LOCAL_GL_LOWER_LEFT);
    }
#endif

    // Check the shader validator pref
    NS_ENSURE_TRUE(Preferences::GetRootBranch(), false);

    mShaderValidation = Preferences::GetBool("webgl.shader_validator",
                                             mShaderValidation);

    // initialize shader translator
    if (mShaderValidation) {
        if (!ShInitialize()) {
            GenerateWarning("GLSL translator initialization failed!");
            return false;
        }
    }

    // Mesa can only be detected with the GL_VERSION string, of the form
    // "2.1 Mesa 7.11.0"
    const char* versionStr = (const char*)(gl->fGetString(LOCAL_GL_VERSION));
    mIsMesa = strstr(versionStr, "Mesa");

    // Notice that the point of calling fGetError here is not only to check for
    // errors, but also to reset the error flags so that a subsequent WebGL
    // getError call will give the correct result.
    error = gl->fGetError();
    if (error != LOCAL_GL_NO_ERROR) {
        GenerateWarning("GL error 0x%x occurred during WebGL context"
                        " initialization!", error);
        return false;
    }

    if (IsWebGL2() &&
        !InitWebGL2())
    {
        // Todo: Bug 898404: Only allow WebGL2 on GL>=3.0 on desktop GL.
        return false;
    }

    // Default value for all disabled vertex attributes is [0, 0, 0, 1]
    for (int32_t index = 0; index < mGLMaxVertexAttribs; ++index) {
        VertexAttrib4f(index, 0, 0, 0, 1);
    }

    mDefaultVertexArray = WebGLVertexArray::Create(this);
    mDefaultVertexArray->mAttribs.SetLength(mGLMaxVertexAttribs);
    mBoundVertexArray = mDefaultVertexArray;

    if (mLoseContextOnMemoryPressure)
        mContextObserver->RegisterMemoryPressureEvent();

    return true;
}

} // namespace mozilla