//
// Copyright 2016 The ANGLE Project Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//

// validationES31.cpp: Validation functions for OpenGL ES 3.1 entry point parameters

#include "libANGLE/validationES31_autogen.h"

#include "libANGLE/Context.h"
#include "libANGLE/ErrorStrings.h"
#include "libANGLE/Framebuffer.h"
#include "libANGLE/ProgramExecutable.h"
#include "libANGLE/VertexArray.h"
#include "libANGLE/validationES.h"
#include "libANGLE/validationES2_autogen.h"
#include "libANGLE/validationES31.h"
#include "libANGLE/validationES3_autogen.h"

#include "common/utilities.h"

using namespace angle;

namespace gl
{
using namespace err;

namespace
{

bool ValidateNamedProgramInterface(GLenum programInterface)
{
    switch (programInterface)
    {
        case GL_UNIFORM:
        case GL_UNIFORM_BLOCK:
        case GL_PROGRAM_INPUT:
        case GL_PROGRAM_OUTPUT:
        case GL_TRANSFORM_FEEDBACK_VARYING:
        case GL_BUFFER_VARIABLE:
        case GL_SHADER_STORAGE_BLOCK:
            return true;
        default:
            return false;
    }
}

bool ValidateLocationProgramInterface(GLenum programInterface)
{
    switch (programInterface)
    {
        case GL_UNIFORM:
        case GL_PROGRAM_INPUT:
        case GL_PROGRAM_OUTPUT:
            return true;
        default:
            return false;
    }
}

bool ValidateProgramInterface(GLenum programInterface)
{
    return (programInterface == GL_ATOMIC_COUNTER_BUFFER ||
            ValidateNamedProgramInterface(programInterface));
}

bool ValidateProgramResourceProperty(const Context *context, GLenum prop)
{
    ASSERT(context);
    switch (prop)
    {
        case GL_ACTIVE_VARIABLES:
        case GL_BUFFER_BINDING:
        case GL_NUM_ACTIVE_VARIABLES:

        case GL_ARRAY_SIZE:

        case GL_ARRAY_STRIDE:
        case GL_BLOCK_INDEX:
        case GL_IS_ROW_MAJOR:
        case GL_MATRIX_STRIDE:

        case GL_ATOMIC_COUNTER_BUFFER_INDEX:

        case GL_BUFFER_DATA_SIZE:

        case GL_LOCATION:

        case GL_NAME_LENGTH:

        case GL_OFFSET:

        case GL_REFERENCED_BY_VERTEX_SHADER:
        case GL_REFERENCED_BY_FRAGMENT_SHADER:
        case GL_REFERENCED_BY_COMPUTE_SHADER:

        case GL_TOP_LEVEL_ARRAY_SIZE:
        case GL_TOP_LEVEL_ARRAY_STRIDE:

        case GL_TYPE:
            return true;

        case GL_REFERENCED_BY_GEOMETRY_SHADER_EXT:
            return context->getExtensions().geometryShader || context->getClientVersion() >= ES_3_2;

        case GL_REFERENCED_BY_TESS_CONTROL_SHADER_EXT:
        case GL_REFERENCED_BY_TESS_EVALUATION_SHADER_EXT:
        case GL_IS_PER_PATCH_EXT:
            return context->getExtensions().tessellationShaderEXT ||
                   context->getClientVersion() >= ES_3_2;

        case GL_LOCATION_INDEX_EXT:
            return context->getExtensions().blendFuncExtended;

        default:
            return false;
    }
}

// GLES 3.10 spec: Page 82 -- Table 7.2
bool ValidateProgramResourcePropertyByInterface(GLenum prop, GLenum programInterface)
{
    switch (prop)
    {
        case GL_ACTIVE_VARIABLES:
        case GL_BUFFER_BINDING:
        case GL_NUM_ACTIVE_VARIABLES:
        {
            switch (programInterface)
            {
                case GL_ATOMIC_COUNTER_BUFFER:
                case GL_SHADER_STORAGE_BLOCK:
                case GL_UNIFORM_BLOCK:
                    return true;
                default:
                    return false;
            }
        }

        case GL_ARRAY_SIZE:
        {
            switch (programInterface)
            {
                case GL_BUFFER_VARIABLE:
                case GL_PROGRAM_INPUT:
                case GL_PROGRAM_OUTPUT:
                case GL_TRANSFORM_FEEDBACK_VARYING:
                case GL_UNIFORM:
                    return true;
                default:
                    return false;
            }
        }

        case GL_ARRAY_STRIDE:
        case GL_BLOCK_INDEX:
        case GL_IS_ROW_MAJOR:
        case GL_MATRIX_STRIDE:
        {
            switch (programInterface)
            {
                case GL_BUFFER_VARIABLE:
                case GL_UNIFORM:
                    return true;
                default:
                    return false;
            }
        }

        case GL_ATOMIC_COUNTER_BUFFER_INDEX:
        {
            if (programInterface == GL_UNIFORM)
            {
                return true;
            }
            return false;
        }

        case GL_BUFFER_DATA_SIZE:
        {
            switch (programInterface)
            {
                case GL_ATOMIC_COUNTER_BUFFER:
                case GL_SHADER_STORAGE_BLOCK:
                case GL_UNIFORM_BLOCK:
                    return true;
                default:
                    return false;
            }
        }

        case GL_LOCATION:
        {
            return ValidateLocationProgramInterface(programInterface);
        }

        case GL_LOCATION_INDEX_EXT:
        {
            // EXT_blend_func_extended
            return (programInterface == GL_PROGRAM_OUTPUT);
        }

        case GL_NAME_LENGTH:
        {
            return ValidateNamedProgramInterface(programInterface);
        }

        case GL_OFFSET:
        {
            switch (programInterface)
            {
                case GL_BUFFER_VARIABLE:
                case GL_UNIFORM:
                    return true;
                default:
                    return false;
            }
        }

        case GL_REFERENCED_BY_VERTEX_SHADER:
        case GL_REFERENCED_BY_FRAGMENT_SHADER:
        case GL_REFERENCED_BY_COMPUTE_SHADER:
        case GL_REFERENCED_BY_GEOMETRY_SHADER_EXT:
        case GL_REFERENCED_BY_TESS_CONTROL_SHADER_EXT:
        case GL_REFERENCED_BY_TESS_EVALUATION_SHADER_EXT:
        {
            switch (programInterface)
            {
                case GL_ATOMIC_COUNTER_BUFFER:
                case GL_BUFFER_VARIABLE:
                case GL_PROGRAM_INPUT:
                case GL_PROGRAM_OUTPUT:
                case GL_SHADER_STORAGE_BLOCK:
                case GL_UNIFORM:
                case GL_UNIFORM_BLOCK:
                    return true;
                default:
                    return false;
            }
        }

        case GL_TOP_LEVEL_ARRAY_SIZE:
        case GL_TOP_LEVEL_ARRAY_STRIDE:
        {
            if (programInterface == GL_BUFFER_VARIABLE)
            {
                return true;
            }
            return false;
        }

        case GL_TYPE:
        {
            switch (programInterface)
            {
                case GL_BUFFER_VARIABLE:
                case GL_PROGRAM_INPUT:
                case GL_PROGRAM_OUTPUT:
                case GL_TRANSFORM_FEEDBACK_VARYING:
                case GL_UNIFORM:
                    return true;
                default:
                    return false;
            }
        }
        case GL_IS_PER_PATCH_EXT:
            switch (programInterface)
            {
                case GL_PROGRAM_INPUT:
                case GL_PROGRAM_OUTPUT:
                    return true;
            }
            return false;

        default:
            return false;
    }
}

bool ValidateProgramResourceIndex(const Program *programObject,
                                  GLenum programInterface,
                                  GLuint index)
{
    switch (programInterface)
    {
        case GL_PROGRAM_INPUT:
            return (index <
                    static_cast<GLuint>(programObject->getState().getProgramInputs().size()));

        case GL_PROGRAM_OUTPUT:
            return (index < static_cast<GLuint>(programObject->getOutputResourceCount()));

        case GL_UNIFORM:
            return (index < static_cast<GLuint>(programObject->getActiveUniformCount()));

        case GL_BUFFER_VARIABLE:
            return (index < static_cast<GLuint>(programObject->getActiveBufferVariableCount()));

        case GL_SHADER_STORAGE_BLOCK:
            return (index < static_cast<GLuint>(programObject->getActiveShaderStorageBlockCount()));

        case GL_UNIFORM_BLOCK:
            return (index < programObject->getActiveUniformBlockCount());

        case GL_ATOMIC_COUNTER_BUFFER:
            return (index < programObject->getActiveAtomicCounterBufferCount());

        case GL_TRANSFORM_FEEDBACK_VARYING:
            return (index < static_cast<GLuint>(programObject->getTransformFeedbackVaryingCount()));

        default:
            UNREACHABLE();
            return false;
    }
}

bool ValidateProgramUniformBase(const Context *context,
                                GLenum valueType,
                                ShaderProgramID program,
                                UniformLocation location,
                                GLsizei count)
{
    const LinkedUniform *uniform = nullptr;
    Program *programObject       = GetValidProgram(context, program);
    return ValidateUniformCommonBase(context, programObject, location, count, &uniform) &&
           ValidateUniformValue(context, valueType, uniform->type);
}

bool ValidateProgramUniformMatrixBase(const Context *context,
                                      GLenum valueType,
                                      ShaderProgramID program,
                                      UniformLocation location,
                                      GLsizei count,
                                      GLboolean transpose)
{
    const LinkedUniform *uniform = nullptr;
    Program *programObject       = GetValidProgram(context, program);
    return ValidateUniformCommonBase(context, programObject, location, count, &uniform) &&
           ValidateUniformMatrixValue(context, valueType, uniform->type);
}

bool ValidateVertexAttribFormatCommon(const Context *context, GLuint relativeOffset)
{
    if (context->getClientVersion() < ES_3_1)
    {
        context->validationError(GL_INVALID_OPERATION, kES31Required);
        return false;
    }

    const Caps &caps = context->getCaps();
    if (relativeOffset > static_cast<GLuint>(caps.maxVertexAttribRelativeOffset))
    {
        context->validationError(GL_INVALID_VALUE, kRelativeOffsetTooLarge);
        return false;
    }

    // [OpenGL ES 3.1] Section 10.3.1 page 243:
    // An INVALID_OPERATION error is generated if the default vertex array object is bound.
    if (context->getState().getVertexArrayId().value == 0)
    {
        context->validationError(GL_INVALID_OPERATION, kDefaultVertexArray);
        return false;
    }

    return true;
}

}  // anonymous namespace

bool ValidateGetBooleani_v(const Context *context,
                           GLenum target,
                           GLuint index,
                           const GLboolean *data)
{
    if (context->getClientVersion() < ES_3_1 && !context->getExtensions().drawBuffersIndexedAny())
    {
        context->validationError(GL_INVALID_OPERATION,
                                 kES31OrDrawBuffersIndexedExtensionNotAvailable);
        return false;
    }

    if (!ValidateIndexedStateQuery(context, target, index, nullptr))
    {
        return false;
    }

    return true;
}

bool ValidateGetBooleani_vRobustANGLE(const Context *context,
                                      GLenum target,
                                      GLuint index,
                                      GLsizei bufSize,
                                      const GLsizei *length,
                                      const GLboolean *data)
{
    if (context->getClientVersion() < ES_3_1 && !context->getExtensions().drawBuffersIndexedAny())
    {
        context->validationError(GL_INVALID_OPERATION,
                                 kES31OrDrawBuffersIndexedExtensionNotAvailable);
        return false;
    }

    if (!ValidateRobustEntryPoint(context, bufSize))
    {
        return false;
    }

    GLsizei numParams = 0;

    if (!ValidateIndexedStateQuery(context, target, index, &numParams))
    {
        return false;
    }

    if (!ValidateRobustBufferSize(context, bufSize, numParams))
    {
        return false;
    }

    SetRobustLengthParam(length, numParams);
    return true;
}

bool ValidateDrawIndirectBase(const Context *context, PrimitiveMode mode, const void *indirect)
{
    if (context->getClientVersion() < ES_3_1)
    {
        context->validationError(GL_INVALID_OPERATION, kES31Required);
        return false;
    }

    // Here the third parameter 1 is only to pass the count validation.
    if (!ValidateDrawBase(context, mode))
    {
        return false;
    }

    const State &state = context->getState();

    // An INVALID_OPERATION error is generated if zero is bound to VERTEX_ARRAY_BINDING,
    // DRAW_INDIRECT_BUFFER or to any enabled vertex array.
    if (state.getVertexArrayId().value == 0)
    {
        context->validationError(GL_INVALID_OPERATION, kDefaultVertexArray);
        return false;
    }

    if (context->getStateCache().hasAnyActiveClientAttrib())
    {
        context->validationError(GL_INVALID_OPERATION, kClientDataInVertexArray);
        return false;
    }

    Buffer *drawIndirectBuffer = state.getTargetBuffer(BufferBinding::DrawIndirect);
    if (!drawIndirectBuffer)
    {
        context->validationError(GL_INVALID_OPERATION, kDrawIndirectBufferNotBound);
        return false;
    }

    // An INVALID_VALUE error is generated if indirect is not a multiple of the size, in basic
    // machine units, of uint.
    GLint64 offset = reinterpret_cast<GLint64>(indirect);
    if ((static_cast<GLuint>(offset) % sizeof(GLuint)) != 0)
    {
        context->validationError(GL_INVALID_VALUE, kInvalidIndirectOffset);
        return false;
    }

    return true;
}

bool ValidateDrawArraysIndirect(const Context *context, PrimitiveMode mode, const void *indirect)
{
    const State &state                      = context->getState();
    TransformFeedback *curTransformFeedback = state.getCurrentTransformFeedback();
    if (curTransformFeedback && curTransformFeedback->isActive() &&
        !curTransformFeedback->isPaused())
    {
        // EXT_geometry_shader allows transform feedback to work with all draw commands.
        // [EXT_geometry_shader] Section 12.1, "Transform Feedback"
        if (context->getExtensions().geometryShader || context->getClientVersion() >= ES_3_2)
        {
            if (!ValidateTransformFeedbackPrimitiveMode(
                    context, curTransformFeedback->getPrimitiveMode(), mode))
            {
                context->validationError(GL_INVALID_OPERATION, kInvalidDrawModeTransformFeedback);
                return false;
            }
        }
        else
        {
            // An INVALID_OPERATION error is generated if transform feedback is active and not
            // paused.
            context->validationError(GL_INVALID_OPERATION,
                                     kUnsupportedDrawModeForTransformFeedback);
            return false;
        }
    }

    if (!ValidateDrawIndirectBase(context, mode, indirect))
        return false;

    Buffer *drawIndirectBuffer = state.getTargetBuffer(BufferBinding::DrawIndirect);
    CheckedNumeric<size_t> checkedOffset(reinterpret_cast<size_t>(indirect));
    // In OpenGL ES3.1 spec, session 10.5, it defines the struct of DrawArraysIndirectCommand
    // which's size is 4 * sizeof(uint).
    auto checkedSum = checkedOffset + 4 * sizeof(GLuint);
    if (!checkedSum.IsValid() ||
        checkedSum.ValueOrDie() > static_cast<size_t>(drawIndirectBuffer->getSize()))
    {
        context->validationError(GL_INVALID_OPERATION, kParamOverflow);
        return false;
    }

    return true;
}

bool ValidateDrawElementsIndirect(const Context *context,
                                  PrimitiveMode mode,
                                  DrawElementsType type,
                                  const void *indirect)
{
    if (!ValidateDrawElementsBase(context, mode, type))
    {
        return false;
    }

    const State &state         = context->getState();
    const VertexArray *vao     = state.getVertexArray();
    Buffer *elementArrayBuffer = vao->getElementArrayBuffer();
    if (!elementArrayBuffer)
    {
        context->validationError(GL_INVALID_OPERATION, kMustHaveElementArrayBinding);
        return false;
    }

    if (!ValidateDrawIndirectBase(context, mode, indirect))
        return false;

    Buffer *drawIndirectBuffer = state.getTargetBuffer(BufferBinding::DrawIndirect);
    CheckedNumeric<size_t> checkedOffset(reinterpret_cast<size_t>(indirect));
    // In OpenGL ES3.1 spec, session 10.5, it defines the struct of DrawElementsIndirectCommand
    // which's size is 5 * sizeof(uint).
    auto checkedSum = checkedOffset + 5 * sizeof(GLuint);
    if (!checkedSum.IsValid() ||
        checkedSum.ValueOrDie() > static_cast<size_t>(drawIndirectBuffer->getSize()))
    {
        context->validationError(GL_INVALID_OPERATION, kParamOverflow);
        return false;
    }

    return true;
}

bool ValidateProgramUniform1iBase(const Context *context,
                                  ShaderProgramID program,
                                  UniformLocation location,
                                  GLint v0)
{
    return ValidateProgramUniform1ivBase(context, program, location, 1, &v0);
}

bool ValidateProgramUniform2iBase(const Context *context,
                                  ShaderProgramID program,
                                  UniformLocation location,
                                  GLint v0,
                                  GLint v1)
{
    GLint xy[2] = {v0, v1};
    return ValidateProgramUniform2ivBase(context, program, location, 1, xy);
}

bool ValidateProgramUniform3iBase(const Context *context,
                                  ShaderProgramID program,
                                  UniformLocation location,
                                  GLint v0,
                                  GLint v1,
                                  GLint v2)
{
    GLint xyz[3] = {v0, v1, v2};
    return ValidateProgramUniform3ivBase(context, program, location, 1, xyz);
}

bool ValidateProgramUniform4iBase(const Context *context,
                                  ShaderProgramID program,
                                  UniformLocation location,
                                  GLint v0,
                                  GLint v1,
                                  GLint v2,
                                  GLint v3)
{
    GLint xyzw[4] = {v0, v1, v2, v3};
    return ValidateProgramUniform4ivBase(context, program, location, 1, xyzw);
}

bool ValidateProgramUniform1uiBase(const Context *context,
                                   ShaderProgramID program,
                                   UniformLocation location,
                                   GLuint v0)
{
    return ValidateProgramUniform1uivBase(context, program, location, 1, &v0);
}

bool ValidateProgramUniform2uiBase(const Context *context,
                                   ShaderProgramID program,
                                   UniformLocation location,
                                   GLuint v0,
                                   GLuint v1)
{
    GLuint xy[2] = {v0, v1};
    return ValidateProgramUniform2uivBase(context, program, location, 1, xy);
}

bool ValidateProgramUniform3uiBase(const Context *context,
                                   ShaderProgramID program,
                                   UniformLocation location,
                                   GLuint v0,
                                   GLuint v1,
                                   GLuint v2)
{
    GLuint xyz[3] = {v0, v1, v2};
    return ValidateProgramUniform3uivBase(context, program, location, 1, xyz);
}

bool ValidateProgramUniform4uiBase(const Context *context,
                                   ShaderProgramID program,
                                   UniformLocation location,
                                   GLuint v0,
                                   GLuint v1,
                                   GLuint v2,
                                   GLuint v3)
{
    GLuint xyzw[4] = {v0, v1, v2, v3};
    return ValidateProgramUniform4uivBase(context, program, location, 1, xyzw);
}

bool ValidateProgramUniform1fBase(const Context *context,
                                  ShaderProgramID program,
                                  UniformLocation location,
                                  GLfloat v0)
{
    return ValidateProgramUniform1fvBase(context, program, location, 1, &v0);
}

bool ValidateProgramUniform2fBase(const Context *context,
                                  ShaderProgramID program,
                                  UniformLocation location,
                                  GLfloat v0,
                                  GLfloat v1)
{
    GLfloat xy[2] = {v0, v1};
    return ValidateProgramUniform2fvBase(context, program, location, 1, xy);
}

bool ValidateProgramUniform3fBase(const Context *context,
                                  ShaderProgramID program,
                                  UniformLocation location,
                                  GLfloat v0,
                                  GLfloat v1,
                                  GLfloat v2)
{
    GLfloat xyz[3] = {v0, v1, v2};
    return ValidateProgramUniform3fvBase(context, program, location, 1, xyz);
}

bool ValidateProgramUniform4fBase(const Context *context,
                                  ShaderProgramID program,
                                  UniformLocation location,
                                  GLfloat v0,
                                  GLfloat v1,
                                  GLfloat v2,
                                  GLfloat v3)
{
    GLfloat xyzw[4] = {v0, v1, v2, v3};
    return ValidateProgramUniform4fvBase(context, program, location, 1, xyzw);
}

bool ValidateProgramUniform1ivBase(const Context *context,
                                   ShaderProgramID program,
                                   UniformLocation location,
                                   GLsizei count,
                                   const GLint *value)
{
    const LinkedUniform *uniform = nullptr;
    Program *programObject       = GetValidProgram(context, program);
    return ValidateUniformCommonBase(context, programObject, location, count, &uniform) &&
           ValidateUniform1ivValue(context, uniform->type, count, value);
}

bool ValidateProgramUniform2ivBase(const Context *context,
                                   ShaderProgramID program,
                                   UniformLocation location,
                                   GLsizei count,
                                   const GLint *value)
{
    return ValidateProgramUniformBase(context, GL_INT_VEC2, program, location, count);
}

bool ValidateProgramUniform3ivBase(const Context *context,
                                   ShaderProgramID program,
                                   UniformLocation location,
                                   GLsizei count,
                                   const GLint *value)
{
    return ValidateProgramUniformBase(context, GL_INT_VEC3, program, location, count);
}

bool ValidateProgramUniform4ivBase(const Context *context,
                                   ShaderProgramID program,
                                   UniformLocation location,
                                   GLsizei count,
                                   const GLint *value)
{
    return ValidateProgramUniformBase(context, GL_INT_VEC4, program, location, count);
}

bool ValidateProgramUniform1uivBase(const Context *context,
                                    ShaderProgramID program,
                                    UniformLocation location,
                                    GLsizei count,
                                    const GLuint *value)
{
    return ValidateProgramUniformBase(context, GL_UNSIGNED_INT, program, location, count);
}

bool ValidateProgramUniform2uivBase(const Context *context,
                                    ShaderProgramID program,
                                    UniformLocation location,
                                    GLsizei count,
                                    const GLuint *value)
{
    return ValidateProgramUniformBase(context, GL_UNSIGNED_INT_VEC2, program, location, count);
}

bool ValidateProgramUniform3uivBase(const Context *context,
                                    ShaderProgramID program,
                                    UniformLocation location,
                                    GLsizei count,
                                    const GLuint *value)
{
    return ValidateProgramUniformBase(context, GL_UNSIGNED_INT_VEC3, program, location, count);
}

bool ValidateProgramUniform4uivBase(const Context *context,
                                    ShaderProgramID program,
                                    UniformLocation location,
                                    GLsizei count,
                                    const GLuint *value)
{
    return ValidateProgramUniformBase(context, GL_UNSIGNED_INT_VEC4, program, location, count);
}

bool ValidateProgramUniform1fvBase(const Context *context,
                                   ShaderProgramID program,
                                   UniformLocation location,
                                   GLsizei count,
                                   const GLfloat *value)
{
    return ValidateProgramUniformBase(context, GL_FLOAT, program, location, count);
}

bool ValidateProgramUniform2fvBase(const Context *context,
                                   ShaderProgramID program,
                                   UniformLocation location,
                                   GLsizei count,
                                   const GLfloat *value)
{
    return ValidateProgramUniformBase(context, GL_FLOAT_VEC2, program, location, count);
}

bool ValidateProgramUniform3fvBase(const Context *context,
                                   ShaderProgramID program,
                                   UniformLocation location,
                                   GLsizei count,
                                   const GLfloat *value)
{
    return ValidateProgramUniformBase(context, GL_FLOAT_VEC3, program, location, count);
}

bool ValidateProgramUniform4fvBase(const Context *context,
                                   ShaderProgramID program,
                                   UniformLocation location,
                                   GLsizei count,
                                   const GLfloat *value)
{
    return ValidateProgramUniformBase(context, GL_FLOAT_VEC4, program, location, count);
}

bool ValidateProgramUniformMatrix2fvBase(const Context *context,
                                         ShaderProgramID program,
                                         UniformLocation location,
                                         GLsizei count,
                                         GLboolean transpose,
                                         const GLfloat *value)
{
    return ValidateProgramUniformMatrixBase(context, GL_FLOAT_MAT2, program, location, count,
                                            transpose);
}

bool ValidateProgramUniformMatrix3fvBase(const Context *context,
                                         ShaderProgramID program,
                                         UniformLocation location,
                                         GLsizei count,
                                         GLboolean transpose,
                                         const GLfloat *value)
{
    return ValidateProgramUniformMatrixBase(context, GL_FLOAT_MAT3, program, location, count,
                                            transpose);
}

bool ValidateProgramUniformMatrix4fvBase(const Context *context,
                                         ShaderProgramID program,
                                         UniformLocation location,
                                         GLsizei count,
                                         GLboolean transpose,
                                         const GLfloat *value)
{
    return ValidateProgramUniformMatrixBase(context, GL_FLOAT_MAT4, program, location, count,
                                            transpose);
}

bool ValidateProgramUniformMatrix2x3fvBase(const Context *context,
                                           ShaderProgramID program,
                                           UniformLocation location,
                                           GLsizei count,
                                           GLboolean transpose,
                                           const GLfloat *value)
{
    return ValidateProgramUniformMatrixBase(context, GL_FLOAT_MAT2x3, program, location, count,
                                            transpose);
}

bool ValidateProgramUniformMatrix3x2fvBase(const Context *context,
                                           ShaderProgramID program,
                                           UniformLocation location,
                                           GLsizei count,
                                           GLboolean transpose,
                                           const GLfloat *value)
{
    return ValidateProgramUniformMatrixBase(context, GL_FLOAT_MAT3x2, program, location, count,
                                            transpose);
}

bool ValidateProgramUniformMatrix2x4fvBase(const Context *context,
                                           ShaderProgramID program,
                                           UniformLocation location,
                                           GLsizei count,
                                           GLboolean transpose,
                                           const GLfloat *value)
{
    return ValidateProgramUniformMatrixBase(context, GL_FLOAT_MAT2x4, program, location, count,
                                            transpose);
}

bool ValidateProgramUniformMatrix4x2fvBase(const Context *context,
                                           ShaderProgramID program,
                                           UniformLocation location,
                                           GLsizei count,
                                           GLboolean transpose,
                                           const GLfloat *value)
{
    return ValidateProgramUniformMatrixBase(context, GL_FLOAT_MAT4x2, program, location, count,
                                            transpose);
}

bool ValidateProgramUniformMatrix3x4fvBase(const Context *context,
                                           ShaderProgramID program,
                                           UniformLocation location,
                                           GLsizei count,
                                           GLboolean transpose,
                                           const GLfloat *value)
{
    return ValidateProgramUniformMatrixBase(context, GL_FLOAT_MAT3x4, program, location, count,
                                            transpose);
}

bool ValidateProgramUniformMatrix4x3fvBase(const Context *context,
                                           ShaderProgramID program,
                                           UniformLocation location,
                                           GLsizei count,
                                           GLboolean transpose,
                                           const GLfloat *value)
{
    return ValidateProgramUniformMatrixBase(context, GL_FLOAT_MAT4x3, program, location, count,
                                            transpose);
}

bool ValidateGetTexLevelParameterfv(const Context *context,
                                    TextureTarget target,
                                    GLint level,
                                    GLenum pname,
                                    const GLfloat *params)
{
    if (context->getClientVersion() < ES_3_1)
    {
        context->validationError(GL_INVALID_OPERATION, kES31Required);
        return false;
    }

    return ValidateGetTexLevelParameterBase(context, target, level, pname, nullptr);
}

bool ValidateGetTexLevelParameterfvRobustANGLE(const Context *context,
                                               TextureTarget target,
                                               GLint level,
                                               GLenum pname,
                                               GLsizei bufSize,
                                               const GLsizei *length,
                                               const GLfloat *params)
{
    UNIMPLEMENTED();
    return false;
}

bool ValidateGetTexLevelParameteriv(const Context *context,
                                    TextureTarget target,
                                    GLint level,
                                    GLenum pname,
                                    const GLint *params)
{
    if (context->getClientVersion() < ES_3_1)
    {
        context->validationError(GL_INVALID_OPERATION, kES31Required);
        return false;
    }

    return ValidateGetTexLevelParameterBase(context, target, level, pname, nullptr);
}

bool ValidateGetTexLevelParameterivRobustANGLE(const Context *context,
                                               TextureTarget target,
                                               GLint level,
                                               GLenum pname,
                                               GLsizei bufSize,
                                               const GLsizei *length,
                                               const GLint *params)
{
    UNIMPLEMENTED();
    return false;
}

bool ValidateTexStorage2DMultisample(const Context *context,
                                     TextureType target,
                                     GLsizei samples,
                                     GLenum internalFormat,
                                     GLsizei width,
                                     GLsizei height,
                                     GLboolean fixedSampleLocations)
{
    if (context->getClientVersion() < ES_3_1)
    {
        context->validationError(GL_INVALID_OPERATION, kES31Required);
        return false;
    }

    return ValidateTexStorage2DMultisampleBase(context, target, samples, internalFormat, width,
                                               height);
}

bool ValidateTexStorageMem2DMultisampleEXT(const Context *context,
                                           TextureType target,
                                           GLsizei samples,
                                           GLenum internalFormat,
                                           GLsizei width,
                                           GLsizei height,
                                           GLboolean fixedSampleLocations,
                                           MemoryObjectID memory,
                                           GLuint64 offset)
{
    if (!context->getExtensions().memoryObject)
    {
        context->validationError(GL_INVALID_OPERATION, kExtensionNotEnabled);
        return false;
    }

    UNIMPLEMENTED();
    return false;
}

bool ValidateGetMultisamplefv(const Context *context,
                              GLenum pname,
                              GLuint index,
                              const GLfloat *val)
{
    if (context->getClientVersion() < ES_3_1)
    {
        context->validationError(GL_INVALID_OPERATION, kES31Required);
        return false;
    }

    return ValidateGetMultisamplefvBase(context, pname, index, val);
}

bool ValidateGetMultisamplefvRobustANGLE(const Context *context,
                                         GLenum pname,
                                         GLuint index,
                                         GLsizei bufSize,
                                         const GLsizei *length,
                                         const GLfloat *val)
{
    UNIMPLEMENTED();
    return false;
}

bool ValidateFramebufferParameteri(const Context *context, GLenum target, GLenum pname, GLint param)
{
    if (context->getClientVersion() < ES_3_1)
    {
        context->validationError(GL_INVALID_OPERATION, kES31Required);
        return false;
    }

    if (!ValidFramebufferTarget(context, target))
    {
        context->validationError(GL_INVALID_ENUM, kInvalidFramebufferTarget);
        return false;
    }

    switch (pname)
    {
        case GL_FRAMEBUFFER_DEFAULT_WIDTH:
        {
            GLint maxWidth = context->getCaps().maxFramebufferWidth;
            if (param < 0 || param > maxWidth)
            {
                context->validationError(GL_INVALID_VALUE, kExceedsFramebufferWidth);
                return false;
            }
            break;
        }
        case GL_FRAMEBUFFER_DEFAULT_HEIGHT:
        {
            GLint maxHeight = context->getCaps().maxFramebufferHeight;
            if (param < 0 || param > maxHeight)
            {
                context->validationError(GL_INVALID_VALUE, kExceedsFramebufferHeight);
                return false;
            }
            break;
        }
        case GL_FRAMEBUFFER_DEFAULT_SAMPLES:
        {
            GLint maxSamples = context->getCaps().maxFramebufferSamples;
            if (param < 0 || param > maxSamples)
            {
                context->validationError(GL_INVALID_VALUE, kExceedsFramebufferSamples);
                return false;
            }
            break;
        }
        case GL_FRAMEBUFFER_DEFAULT_FIXED_SAMPLE_LOCATIONS:
        {
            break;
        }
        case GL_FRAMEBUFFER_DEFAULT_LAYERS_EXT:
        {
            if (!context->getExtensions().geometryShader && context->getClientVersion() < ES_3_2)
            {
                context->validationError(GL_INVALID_ENUM, kGeometryShaderExtensionNotEnabled);
                return false;
            }
            GLint maxLayers = context->getCaps().maxFramebufferLayers;
            if (param < 0 || param > maxLayers)
            {
                context->validationError(GL_INVALID_VALUE, kInvalidFramebufferLayer);
                return false;
            }
            break;
        }
        default:
        {
            context->validationError(GL_INVALID_ENUM, kInvalidPname);
            return false;
        }
    }

    const Framebuffer *framebuffer = context->getState().getTargetFramebuffer(target);
    ASSERT(framebuffer);
    if (framebuffer->isDefault())
    {
        context->validationError(GL_INVALID_OPERATION, kDefaultFramebuffer);
        return false;
    }
    return true;
}

bool ValidateGetFramebufferParameteriv(const Context *context,
                                       GLenum target,
                                       GLenum pname,
                                       const GLint *params)
{
    if (context->getClientVersion() < ES_3_1)
    {
        context->validationError(GL_INVALID_OPERATION, kES31Required);
        return false;
    }

    if (!ValidFramebufferTarget(context, target))
    {
        context->validationError(GL_INVALID_ENUM, kInvalidFramebufferTarget);
        return false;
    }

    switch (pname)
    {
        case GL_FRAMEBUFFER_DEFAULT_WIDTH:
        case GL_FRAMEBUFFER_DEFAULT_HEIGHT:
        case GL_FRAMEBUFFER_DEFAULT_SAMPLES:
        case GL_FRAMEBUFFER_DEFAULT_FIXED_SAMPLE_LOCATIONS:
            break;
        case GL_FRAMEBUFFER_DEFAULT_LAYERS_EXT:
            if (!context->getExtensions().geometryShader && context->getClientVersion() < ES_3_2)
            {
                context->validationError(GL_INVALID_ENUM, kGeometryShaderExtensionNotEnabled);
                return false;
            }
            break;
        default:
            context->validationError(GL_INVALID_ENUM, kInvalidPname);
            return false;
    }

    const Framebuffer *framebuffer = context->getState().getTargetFramebuffer(target);
    ASSERT(framebuffer);

    if (framebuffer->isDefault())
    {
        context->validationError(GL_INVALID_OPERATION, kDefaultFramebuffer);
        return false;
    }
    return true;
}

bool ValidateGetFramebufferParameterivRobustANGLE(const Context *context,
                                                  GLenum target,
                                                  GLenum pname,
                                                  GLsizei bufSize,
                                                  const GLsizei *length,
                                                  const GLint *params)
{
    UNIMPLEMENTED();
    return false;
}

bool ValidateGetProgramResourceIndex(const Context *context,
                                     ShaderProgramID program,
                                     GLenum programInterface,
                                     const GLchar *name)
{
    if (context->getClientVersion() < ES_3_1)
    {
        context->validationError(GL_INVALID_OPERATION, kES31Required);
        return false;
    }

    Program *programObject = GetValidProgram(context, program);
    if (programObject == nullptr)
    {
        return false;
    }

    if (!ValidateNamedProgramInterface(programInterface))
    {
        context->validationError(GL_INVALID_ENUM, kInvalidProgramInterface);
        return false;
    }

    return true;
}

bool ValidateBindVertexBuffer(const Context *context,
                              GLuint bindingIndex,
                              BufferID buffer,
                              GLintptr offset,
                              GLsizei stride)
{
    if (context->getClientVersion() < ES_3_1)
    {
        context->validationError(GL_INVALID_OPERATION, kES31Required);
        return false;
    }

    if (!context->isBufferGenerated(buffer))
    {
        context->validationError(GL_INVALID_OPERATION, kObjectNotGenerated);
        return false;
    }

    const Caps &caps = context->getCaps();
    if (bindingIndex >= static_cast<GLuint>(caps.maxVertexAttribBindings))
    {
        context->validationError(GL_INVALID_VALUE, kExceedsMaxVertexAttribBindings);
        return false;
    }

    if (offset < 0)
    {
        context->validationError(GL_INVALID_VALUE, kNegativeOffset);
        return false;
    }

    if (stride < 0 || stride > caps.maxVertexAttribStride)
    {
        context->validationError(GL_INVALID_VALUE, kExceedsMaxVertexAttribStride);
        return false;
    }

    // [OpenGL ES 3.1] Section 10.3.1 page 244:
    // An INVALID_OPERATION error is generated if the default vertex array object is bound.
    if (context->getState().getVertexArrayId().value == 0)
    {
        context->validationError(GL_INVALID_OPERATION, kDefaultVertexArray);
        return false;
    }

    return true;
}

bool ValidateVertexBindingDivisor(const Context *context, GLuint bindingIndex, GLuint divisor)
{
    if (context->getClientVersion() < ES_3_1)
    {
        context->validationError(GL_INVALID_OPERATION, kES31Required);
        return false;
    }

    const Caps &caps = context->getCaps();
    if (bindingIndex >= static_cast<GLuint>(caps.maxVertexAttribBindings))
    {
        context->validationError(GL_INVALID_VALUE, kExceedsMaxVertexAttribBindings);
        return false;
    }

    // [OpenGL ES 3.1] Section 10.3.1 page 243:
    // An INVALID_OPERATION error is generated if the default vertex array object is bound.
    if (context->getState().getVertexArrayId().value == 0)
    {
        context->validationError(GL_INVALID_OPERATION, kDefaultVertexArray);
        return false;
    }

    return true;
}

bool ValidateVertexAttribFormat(const Context *context,
                                GLuint attribindex,
                                GLint size,
                                VertexAttribType type,
                                GLboolean normalized,
                                GLuint relativeoffset)
{
    if (!ValidateVertexAttribFormatCommon(context, relativeoffset))
    {
        return false;
    }

    return ValidateFloatVertexFormat(context, attribindex, size, type);
}

bool ValidateVertexAttribIFormat(const Context *context,
                                 GLuint attribindex,
                                 GLint size,
                                 VertexAttribType type,
                                 GLuint relativeoffset)
{
    if (!ValidateVertexAttribFormatCommon(context, relativeoffset))
    {
        return false;
    }

    return ValidateIntegerVertexFormat(context, attribindex, size, type);
}

bool ValidateVertexAttribBinding(const Context *context, GLuint attribIndex, GLuint bindingIndex)
{
    if (context->getClientVersion() < ES_3_1)
    {
        context->validationError(GL_INVALID_OPERATION, kES31Required);
        return false;
    }

    // [OpenGL ES 3.1] Section 10.3.1 page 243:
    // An INVALID_OPERATION error is generated if the default vertex array object is bound.
    if (context->getState().getVertexArrayId().value == 0)
    {
        context->validationError(GL_INVALID_OPERATION, kDefaultVertexArray);
        return false;
    }

    const Caps &caps = context->getCaps();
    if (attribIndex >= static_cast<GLuint>(caps.maxVertexAttributes))
    {
        context->validationError(GL_INVALID_VALUE, kIndexExceedsMaxVertexAttribute);
        return false;
    }

    if (bindingIndex >= static_cast<GLuint>(caps.maxVertexAttribBindings))
    {
        context->validationError(GL_INVALID_VALUE, kExceedsMaxVertexAttribBindings);
        return false;
    }

    return true;
}

bool ValidateGetProgramResourceName(const Context *context,
                                    ShaderProgramID program,
                                    GLenum programInterface,
                                    GLuint index,
                                    GLsizei bufSize,
                                    const GLsizei *length,
                                    const GLchar *name)
{
    if (context->getClientVersion() < ES_3_1)
    {
        context->validationError(GL_INVALID_OPERATION, kES31Required);
        return false;
    }

    Program *programObject = GetValidProgram(context, program);
    if (programObject == nullptr)
    {
        return false;
    }

    if (!ValidateNamedProgramInterface(programInterface))
    {
        context->validationError(GL_INVALID_ENUM, kInvalidProgramInterface);
        return false;
    }

    if (!ValidateProgramResourceIndex(programObject, programInterface, index))
    {
        context->validationError(GL_INVALID_VALUE, kInvalidProgramResourceIndex);
        return false;
    }

    if (bufSize < 0)
    {
        context->validationError(GL_INVALID_VALUE, kNegativeBufferSize);
        return false;
    }

    return true;
}

bool ValidateDispatchCompute(const Context *context,
                             GLuint numGroupsX,
                             GLuint numGroupsY,
                             GLuint numGroupsZ)
{
    if (context->getClientVersion() < ES_3_1)
    {
        context->validationError(GL_INVALID_OPERATION, kES31Required);
        return false;
    }

    const State &state                  = context->getState();
    const ProgramExecutable *executable = state.getProgramExecutable();

    if (executable == nullptr || !executable->hasLinkedShaderStage(ShaderType::Compute))
    {
        context->validationError(GL_INVALID_OPERATION, kNoActiveProgramWithComputeShader);
        return false;
    }

    const Caps &caps = context->getCaps();
    if (numGroupsX > static_cast<GLuint>(caps.maxComputeWorkGroupCount[0]))
    {
        context->validationError(GL_INVALID_VALUE, kExceedsComputeWorkGroupCountX);
        return false;
    }
    if (numGroupsY > static_cast<GLuint>(caps.maxComputeWorkGroupCount[1]))
    {
        context->validationError(GL_INVALID_VALUE, kExceedsComputeWorkGroupCountY);
        return false;
    }
    if (numGroupsZ > static_cast<GLuint>(caps.maxComputeWorkGroupCount[2]))
    {
        context->validationError(GL_INVALID_VALUE, kExceedsComputeWorkGroupCountZ);
        return false;
    }

    return true;
}

bool ValidateDispatchComputeIndirect(const Context *context, GLintptr indirect)
{
    if (context->getClientVersion() < ES_3_1)
    {
        context->validationError(GL_INVALID_OPERATION, kES31Required);
        return false;
    }

    const State &state                  = context->getState();
    const ProgramExecutable *executable = state.getProgramExecutable();

    if (executable == nullptr || !executable->hasLinkedShaderStage(ShaderType::Compute))
    {
        context->validationError(GL_INVALID_OPERATION, kNoActiveProgramWithComputeShader);
        return false;
    }

    if (indirect < 0)
    {
        context->validationError(GL_INVALID_VALUE, kNegativeOffset);
        return false;
    }

    if ((indirect & (sizeof(GLuint) - 1)) != 0)
    {
        context->validationError(GL_INVALID_VALUE, kOffsetMustBeMultipleOfUint);
        return false;
    }

    Buffer *dispatchIndirectBuffer = state.getTargetBuffer(BufferBinding::DispatchIndirect);
    if (!dispatchIndirectBuffer)
    {
        context->validationError(GL_INVALID_OPERATION, kDispatchIndirectBufferNotBound);
        return false;
    }

    CheckedNumeric<GLuint64> checkedOffset(static_cast<GLuint64>(indirect));
    auto checkedSum = checkedOffset + static_cast<GLuint64>(3 * sizeof(GLuint));
    if (!checkedSum.IsValid() ||
        checkedSum.ValueOrDie() > static_cast<GLuint64>(dispatchIndirectBuffer->getSize()))
    {
        context->validationError(GL_INVALID_OPERATION, kInsufficientBufferSize);
        return false;
    }

    return true;
}

bool ValidateBindImageTexture(const Context *context,
                              GLuint unit,
                              TextureID texture,
                              GLint level,
                              GLboolean layered,
                              GLint layer,
                              GLenum access,
                              GLenum format)
{
    if (context->getClientVersion() < ES_3_1)
    {
        context->validationError(GL_INVALID_OPERATION, kES31Required);
        return false;
    }

    GLuint maxImageUnits = static_cast<GLuint>(context->getCaps().maxImageUnits);
    if (unit >= maxImageUnits)
    {
        context->validationError(GL_INVALID_VALUE, kExceedsMaxImageUnits);
        return false;
    }

    if (level < 0)
    {
        context->validationError(GL_INVALID_VALUE, kNegativeLevel);
        return false;
    }

    if (layer < 0)
    {
        context->validationError(GL_INVALID_VALUE, kNegativeLayer);
        return false;
    }

    if (access != GL_READ_ONLY && access != GL_WRITE_ONLY && access != GL_READ_WRITE)
    {
        context->validationError(GL_INVALID_ENUM, kInvalidImageAccess);
        return false;
    }

    switch (format)
    {
        case GL_RGBA32F:
        case GL_RGBA16F:
        case GL_R32F:
        case GL_RGBA32UI:
        case GL_RGBA16UI:
        case GL_RGBA8UI:
        case GL_R32UI:
        case GL_RGBA32I:
        case GL_RGBA16I:
        case GL_RGBA8I:
        case GL_R32I:
        case GL_RGBA8:
        case GL_RGBA8_SNORM:
            break;
        default:
            context->validationError(GL_INVALID_VALUE, kInvalidImageFormat);
            return false;
    }

    if (texture.value != 0)
    {
        Texture *tex = context->getTexture(texture);

        if (tex == nullptr)
        {
            context->validationError(GL_INVALID_VALUE, kMissingTextureName);
            return false;
        }

        if (!tex->getImmutableFormat())
        {
            context->validationError(GL_INVALID_OPERATION, kTextureIsNotImmutable);
            return false;
        }
    }

    return true;
}

bool ValidateGetProgramResourceLocation(const Context *context,
                                        ShaderProgramID program,
                                        GLenum programInterface,
                                        const GLchar *name)
{
    if (context->getClientVersion() < ES_3_1)
    {
        context->validationError(GL_INVALID_OPERATION, kES31Required);
        return false;
    }

    Program *programObject = GetValidProgram(context, program);
    if (programObject == nullptr)
    {
        return false;
    }

    if (!programObject->isLinked())
    {
        context->validationError(GL_INVALID_OPERATION, kProgramNotLinked);
        return false;
    }

    if (!ValidateLocationProgramInterface(programInterface))
    {
        context->validationError(GL_INVALID_ENUM, kInvalidProgramInterface);
        return false;
    }
    return true;
}

bool ValidateGetProgramResourceiv(const Context *context,
                                  ShaderProgramID program,
                                  GLenum programInterface,
                                  GLuint index,
                                  GLsizei propCount,
                                  const GLenum *props,
                                  GLsizei bufSize,
                                  const GLsizei *length,
                                  const GLint *params)
{
    if (context->getClientVersion() < ES_3_1)
    {
        context->validationError(GL_INVALID_OPERATION, kES31Required);
        return false;
    }

    Program *programObject = GetValidProgram(context, program);
    if (programObject == nullptr)
    {
        return false;
    }
    if (!ValidateProgramInterface(programInterface))
    {
        context->validationError(GL_INVALID_ENUM, kInvalidProgramInterface);
        return false;
    }
    if (propCount <= 0)
    {
        context->validationError(GL_INVALID_VALUE, kInvalidPropCount);
        return false;
    }
    if (bufSize < 0)
    {
        context->validationError(GL_INVALID_VALUE, kNegativeBufSize);
        return false;
    }
    if (!ValidateProgramResourceIndex(programObject, programInterface, index))
    {
        context->validationError(GL_INVALID_VALUE, kInvalidProgramResourceIndex);
        return false;
    }
    for (GLsizei i = 0; i < propCount; i++)
    {
        if (!ValidateProgramResourceProperty(context, props[i]))
        {
            context->validationError(GL_INVALID_ENUM, kInvalidProgramResourceProperty);
            return false;
        }
        if (!ValidateProgramResourcePropertyByInterface(props[i], programInterface))
        {
            context->validationError(GL_INVALID_OPERATION, kInvalidPropertyForProgramInterface);
            return false;
        }
    }
    return true;
}

bool ValidateGetProgramInterfaceiv(const Context *context,
                                   ShaderProgramID program,
                                   GLenum programInterface,
                                   GLenum pname,
                                   const GLint *params)
{
    if (context->getClientVersion() < ES_3_1)
    {
        context->validationError(GL_INVALID_OPERATION, kES31Required);
        return false;
    }

    Program *programObject = GetValidProgram(context, program);
    if (programObject == nullptr)
    {
        return false;
    }

    if (!ValidateProgramInterface(programInterface))
    {
        context->validationError(GL_INVALID_ENUM, kInvalidProgramInterface);
        return false;
    }

    switch (pname)
    {
        case GL_ACTIVE_RESOURCES:
        case GL_MAX_NAME_LENGTH:
        case GL_MAX_NUM_ACTIVE_VARIABLES:
            break;

        default:
            context->validationError(GL_INVALID_ENUM, kInvalidPname);
            return false;
    }

    if (pname == GL_MAX_NAME_LENGTH && programInterface == GL_ATOMIC_COUNTER_BUFFER)
    {
        context->validationError(GL_INVALID_OPERATION, kAtomicCounterResourceName);
        return false;
    }

    if (pname == GL_MAX_NUM_ACTIVE_VARIABLES)
    {
        switch (programInterface)
        {
            case GL_ATOMIC_COUNTER_BUFFER:
            case GL_SHADER_STORAGE_BLOCK:
            case GL_UNIFORM_BLOCK:
                break;

            default:
                context->validationError(GL_INVALID_OPERATION, kMaxActiveVariablesInterface);
                return false;
        }
    }

    return true;
}

bool ValidateGetProgramInterfaceivRobustANGLE(const Context *context,
                                              ShaderProgramID program,
                                              GLenum programInterface,
                                              GLenum pname,
                                              GLsizei bufSize,
                                              const GLsizei *length,
                                              const GLint *params)
{
    UNIMPLEMENTED();
    return false;
}

bool ValidateGenProgramPipelinesBase(const Context *context,
                                     GLsizei n,
                                     const ProgramPipelineID *pipelines)
{
    return ValidateGenOrDelete(context, n);
}

bool ValidateDeleteProgramPipelinesBase(const Context *context,
                                        GLsizei n,
                                        const ProgramPipelineID *pipelines)
{
    return ValidateGenOrDelete(context, n);
}

bool ValidateBindProgramPipelineBase(const Context *context, ProgramPipelineID pipeline)
{
    if (!context->isProgramPipelineGenerated({pipeline}))
    {
        context->validationError(GL_INVALID_OPERATION, kObjectNotGenerated);
        return false;
    }

    return true;
}

bool ValidateIsProgramPipelineBase(const Context *context, ProgramPipelineID pipeline)
{
    return true;
}

bool ValidateUseProgramStagesBase(const Context *context,
                                  ProgramPipelineID pipeline,
                                  GLbitfield stages,
                                  ShaderProgramID programId)
{
    // GL_INVALID_VALUE is generated if shaders contains set bits that are not recognized, and is
    // not the reserved value GL_ALL_SHADER_BITS.
    GLbitfield knownShaderBits =
        GL_VERTEX_SHADER_BIT | GL_FRAGMENT_SHADER_BIT | GL_COMPUTE_SHADER_BIT;

    if (context->getClientVersion() >= ES_3_2 || context->getExtensions().geometryShader)
    {
        knownShaderBits |= GL_GEOMETRY_SHADER_BIT;
    }

    if (context->getClientVersion() >= ES_3_2 || context->getExtensions().tessellationShaderEXT)
    {
        knownShaderBits |= GL_TESS_CONTROL_SHADER_BIT;
        knownShaderBits |= GL_TESS_EVALUATION_SHADER_BIT;
    }

    if ((stages & ~knownShaderBits) && (stages != GL_ALL_SHADER_BITS))
    {
        context->validationError(GL_INVALID_VALUE, kUnrecognizedShaderStageBit);
        return false;
    }

    // GL_INVALID_OPERATION is generated if pipeline is not a name previously returned from a call
    // to glGenProgramPipelines or if such a name has been deleted by a call to
    // glDeleteProgramPipelines.
    if (!context->isProgramPipelineGenerated({pipeline}))
    {
        context->validationError(GL_INVALID_OPERATION, kObjectNotGenerated);
        return false;
    }

    // If program is zero, or refers to a program object with no valid shader executable for a given
    // stage, it is as if the pipeline object has no programmable stage configured for the indicated
    // shader stages.
    if (programId.value == 0)
    {
        return true;
    }

    Program *program = context->getProgramNoResolveLink(programId);
    if (!program)
    {
        context->validationError(GL_INVALID_VALUE, kProgramDoesNotExist);
        return false;
    }

    // GL_INVALID_OPERATION is generated if program refers to a program object that was not linked
    // with its GL_PROGRAM_SEPARABLE status set.
    // resolveLink() may not have been called if glCreateShaderProgramv() was not used and
    // glDetachShader() was not called.
    program->resolveLink(context);
    if (!program->isSeparable())
    {
        context->validationError(GL_INVALID_OPERATION, kProgramNotSeparable);
        return false;
    }

    // GL_INVALID_OPERATION is generated if program refers to a program object that has not been
    // successfully linked.
    if (!program->isLinked())
    {
        context->validationError(GL_INVALID_OPERATION, kProgramNotLinked);
        return false;
    }

    return true;
}

bool ValidateActiveShaderProgramBase(const Context *context,
                                     ProgramPipelineID pipeline,
                                     ShaderProgramID programId)
{
    // An INVALID_OPERATION error is generated if pipeline is not a name returned from a previous
    // call to GenProgramPipelines or if such a name has since been deleted by
    // DeleteProgramPipelines.
    if (!context->isProgramPipelineGenerated({pipeline}))
    {
        context->validationError(GL_INVALID_OPERATION, kObjectNotGenerated);
        return false;
    }

    // An INVALID_VALUE error is generated if program is not zero and is not the name of either a
    // program or shader object.
    if ((programId.value != 0) && !context->isProgram(programId) && !context->isShader(programId))
    {
        context->validationError(GL_INVALID_VALUE, kProgramDoesNotExist);
        return false;
    }

    // An INVALID_OPERATION error is generated if program is the name of a shader object.
    if (context->isShader(programId))
    {
        context->validationError(GL_INVALID_OPERATION, kExpectedProgramName);
        return false;
    }

    // An INVALID_OPERATION error is generated if program is not zero and has not been linked, or
    // was last linked unsuccessfully. The active program is not modified.
    Program *program = context->getProgramNoResolveLink(programId);
    if ((programId.value != 0) && !program->isLinked())
    {
        context->validationError(GL_INVALID_OPERATION, kProgramNotLinked);
        return false;
    }

    return true;
}

bool ValidateCreateShaderProgramvBase(const Context *context,
                                      ShaderType type,
                                      GLsizei count,
                                      const GLchar *const *strings)
{
    switch (type)
    {
        case ShaderType::InvalidEnum:
            context->validationError(GL_INVALID_ENUM, kInvalidShaderType);
            return false;
        case ShaderType::Vertex:
        case ShaderType::Fragment:
        case ShaderType::Compute:
            break;
        case ShaderType::Geometry:
            if (!context->getExtensions().geometryShader && context->getClientVersion() < ES_3_2)
            {
                context->validationError(GL_INVALID_ENUM, kInvalidShaderType);
                return false;
            }
            break;
        case ShaderType::TessControl:
        case ShaderType::TessEvaluation:
            if (!context->getExtensions().tessellationShaderEXT &&
                context->getClientVersion() < ES_3_2)
            {
                context->validationError(GL_INVALID_ENUM, kInvalidShaderType);
                return false;
            }
            break;
        default:
            UNREACHABLE();
    }

    // GL_INVALID_VALUE is generated if count is negative.
    if (count < 0)
    {
        context->validationError(GL_INVALID_VALUE, kNegativeCount);
        return false;
    }

    return true;
}

bool ValidateCreateShaderProgramvBase(const Context *context,
                                      ShaderType type,
                                      GLsizei count,
                                      const GLchar **strings)
{
    const GLchar *const *tmpStrings = strings;
    return ValidateCreateShaderProgramvBase(context, type, count, tmpStrings);
}

bool ValidateGetProgramPipelineivBase(const Context *context,
                                      ProgramPipelineID pipeline,
                                      GLenum pname,
                                      const GLint *params)
{
    // An INVALID_OPERATION error is generated if pipeline is not a name returned from a previous
    // call to GenProgramPipelines or if such a name has since been deleted by
    // DeleteProgramPipelines.
    if ((pipeline.value == 0) || (!context->isProgramPipelineGenerated(pipeline)))
    {
        context->validationError(GL_INVALID_OPERATION, kProgramPipelineDoesNotExist);
        return false;
    }

    // An INVALID_ENUM error is generated if pname is not ACTIVE_PROGRAM,
    // INFO_LOG_LENGTH, VALIDATE_STATUS, or one of the type arguments in
    // table 7.1.
    switch (pname)
    {
        case GL_ACTIVE_PROGRAM:
        case GL_INFO_LOG_LENGTH:
        case GL_VALIDATE_STATUS:
        case GL_VERTEX_SHADER:
        case GL_FRAGMENT_SHADER:
        case GL_COMPUTE_SHADER:
            break;
        case GL_GEOMETRY_SHADER:
            return context->getExtensions().geometryShader || context->getClientVersion() >= ES_3_2;
        case GL_TESS_CONTROL_SHADER:
        case GL_TESS_EVALUATION_SHADER:
            return context->getExtensions().tessellationShaderEXT ||
                   context->getClientVersion() >= ES_3_2;

        default:
            context->validationError(GL_INVALID_ENUM, kInvalidPname);
            return false;
    }

    return true;
}

bool ValidateValidateProgramPipelineBase(const Context *context, ProgramPipelineID pipeline)
{
    if (pipeline.value == 0)
    {
        return false;
    }

    if (!context->isProgramPipelineGenerated(pipeline))
    {
        context->validationError(GL_INVALID_OPERATION, kProgramPipelineDoesNotExist);
        return false;
    }

    return true;
}

bool ValidateGetProgramPipelineInfoLogBase(const Context *context,
                                           ProgramPipelineID pipeline,
                                           GLsizei bufSize,
                                           const GLsizei *length,
                                           const GLchar *infoLog)
{
    if (bufSize < 0)
    {
        context->validationError(GL_INVALID_VALUE, kNegativeBufferSize);
        return false;
    }

    if (!context->isProgramPipelineGenerated(pipeline))
    {
        context->validationError(GL_INVALID_VALUE, kProgramPipelineDoesNotExist);
        return false;
    }

    return true;
}

bool ValidateActiveShaderProgram(const Context *context,
                                 ProgramPipelineID pipelinePacked,
                                 ShaderProgramID programPacked)
{
    if (context->getClientVersion() < ES_3_1)
    {
        context->validationError(GL_INVALID_OPERATION, kES31Required);
        return false;
    }

    return ValidateActiveShaderProgramBase(context, pipelinePacked, programPacked);
}

bool ValidateBindProgramPipeline(const Context *context, ProgramPipelineID pipelinePacked)
{
    if (context->getClientVersion() < ES_3_1)
    {
        context->validationError(GL_INVALID_OPERATION, kES31Required);
        return false;
    }

    return ValidateBindProgramPipelineBase(context, pipelinePacked);
}

bool ValidateCreateShaderProgramv(const Context *context,
                                  ShaderType typePacked,
                                  GLsizei count,
                                  const GLchar *const *strings)
{
    if (context->getClientVersion() < ES_3_1)
    {
        context->validationError(GL_INVALID_OPERATION, kES31Required);
        return false;
    }

    return ValidateCreateShaderProgramvBase(context, typePacked, count, strings);
}

bool ValidateDeleteProgramPipelines(const Context *context,
                                    GLsizei n,
                                    const ProgramPipelineID *pipelinesPacked)
{
    if (context->getClientVersion() < ES_3_1)
    {
        context->validationError(GL_INVALID_OPERATION, kES31Required);
        return false;
    }

    return ValidateDeleteProgramPipelinesBase(context, n, pipelinesPacked);
}

bool ValidateGenProgramPipelines(const Context *context,
                                 GLsizei n,
                                 const ProgramPipelineID *pipelinesPacked)
{
    if (context->getClientVersion() < ES_3_1)
    {
        context->validationError(GL_INVALID_OPERATION, kES31Required);
        return false;
    }

    return ValidateGenProgramPipelinesBase(context, n, pipelinesPacked);
}

bool ValidateGetProgramPipelineInfoLog(const Context *context,
                                       ProgramPipelineID pipelinePacked,
                                       GLsizei bufSize,
                                       const GLsizei *length,
                                       const GLchar *infoLog)
{
    if (context->getClientVersion() < ES_3_1)
    {
        context->validationError(GL_INVALID_OPERATION, kES31Required);
        return false;
    }

    return ValidateGetProgramPipelineInfoLogBase(context, pipelinePacked, bufSize, length, infoLog);
}

bool ValidateGetProgramPipelineiv(const Context *context,
                                  ProgramPipelineID pipelinePacked,
                                  GLenum pname,
                                  const GLint *params)
{
    if (context->getClientVersion() < ES_3_1)
    {
        context->validationError(GL_INVALID_OPERATION, kES31Required);
        return false;
    }

    return ValidateGetProgramPipelineivBase(context, pipelinePacked, pname, params);
}

bool ValidateIsProgramPipeline(const Context *context, ProgramPipelineID pipelinePacked)
{
    if (context->getClientVersion() < ES_3_1)
    {
        context->validationError(GL_INVALID_OPERATION, kES31Required);
        return false;
    }

    return ValidateIsProgramPipelineBase(context, pipelinePacked);
}

bool ValidateProgramUniform1f(const Context *context,
                              ShaderProgramID programPacked,
                              UniformLocation locationPacked,
                              GLfloat v0)
{
    if (context->getClientVersion() < ES_3_1)
    {
        context->validationError(GL_INVALID_OPERATION, kES31Required);
        return false;
    }

    return ValidateProgramUniform1fBase(context, programPacked, locationPacked, v0);
}

bool ValidateProgramUniform1fv(const Context *context,
                               ShaderProgramID programPacked,
                               UniformLocation locationPacked,
                               GLsizei count,
                               const GLfloat *value)
{
    if (context->getClientVersion() < ES_3_1)
    {
        context->validationError(GL_INVALID_OPERATION, kES31Required);
        return false;
    }

    return ValidateProgramUniform1fvBase(context, programPacked, locationPacked, count, value);
}

bool ValidateProgramUniform1i(const Context *context,
                              ShaderProgramID programPacked,
                              UniformLocation locationPacked,
                              GLint v0)
{
    if (context->getClientVersion() < ES_3_1)
    {
        context->validationError(GL_INVALID_OPERATION, kES31Required);
        return false;
    }

    return ValidateProgramUniform1iBase(context, programPacked, locationPacked, v0);
}

bool ValidateProgramUniform1iv(const Context *context,
                               ShaderProgramID programPacked,
                               UniformLocation locationPacked,
                               GLsizei count,
                               const GLint *value)
{
    if (context->getClientVersion() < ES_3_1)
    {
        context->validationError(GL_INVALID_OPERATION, kES31Required);
        return false;
    }

    return ValidateProgramUniform1ivBase(context, programPacked, locationPacked, count, value);
}

bool ValidateProgramUniform1ui(const Context *context,
                               ShaderProgramID programPacked,
                               UniformLocation locationPacked,
                               GLuint v0)
{
    if (context->getClientVersion() < ES_3_1)
    {
        context->validationError(GL_INVALID_OPERATION, kES31Required);
        return false;
    }

    return ValidateProgramUniform1uiBase(context, programPacked, locationPacked, v0);
}

bool ValidateProgramUniform1uiv(const Context *context,
                                ShaderProgramID programPacked,
                                UniformLocation locationPacked,
                                GLsizei count,
                                const GLuint *value)
{
    if (context->getClientVersion() < ES_3_1)
    {
        context->validationError(GL_INVALID_OPERATION, kES31Required);
        return false;
    }

    return ValidateProgramUniform1uivBase(context, programPacked, locationPacked, count, value);
}

bool ValidateProgramUniform2f(const Context *context,
                              ShaderProgramID programPacked,
                              UniformLocation locationPacked,
                              GLfloat v0,
                              GLfloat v1)
{
    if (context->getClientVersion() < ES_3_1)
    {
        context->validationError(GL_INVALID_OPERATION, kES31Required);
        return false;
    }

    return ValidateProgramUniform2fBase(context, programPacked, locationPacked, v0, v1);
}

bool ValidateProgramUniform2fv(const Context *context,
                               ShaderProgramID programPacked,
                               UniformLocation locationPacked,
                               GLsizei count,
                               const GLfloat *value)
{
    if (context->getClientVersion() < ES_3_1)
    {
        context->validationError(GL_INVALID_OPERATION, kES31Required);
        return false;
    }

    return ValidateProgramUniform2fvBase(context, programPacked, locationPacked, count, value);
}

bool ValidateProgramUniform2i(const Context *context,
                              ShaderProgramID programPacked,
                              UniformLocation locationPacked,
                              GLint v0,
                              GLint v1)
{
    if (context->getClientVersion() < ES_3_1)
    {
        context->validationError(GL_INVALID_OPERATION, kES31Required);
        return false;
    }

    return ValidateProgramUniform2iBase(context, programPacked, locationPacked, v0, v1);
}

bool ValidateProgramUniform2iv(const Context *context,
                               ShaderProgramID programPacked,
                               UniformLocation locationPacked,
                               GLsizei count,
                               const GLint *value)
{
    if (context->getClientVersion() < ES_3_1)
    {
        context->validationError(GL_INVALID_OPERATION, kES31Required);
        return false;
    }

    return ValidateProgramUniform2ivBase(context, programPacked, locationPacked, count, value);
}

bool ValidateProgramUniform2ui(const Context *context,
                               ShaderProgramID programPacked,
                               UniformLocation locationPacked,
                               GLuint v0,
                               GLuint v1)
{
    if (context->getClientVersion() < ES_3_1)
    {
        context->validationError(GL_INVALID_OPERATION, kES31Required);
        return false;
    }

    return ValidateProgramUniform2uiBase(context, programPacked, locationPacked, v0, v1);
}

bool ValidateProgramUniform2uiv(const Context *context,
                                ShaderProgramID programPacked,
                                UniformLocation locationPacked,
                                GLsizei count,
                                const GLuint *value)
{
    if (context->getClientVersion() < ES_3_1)
    {
        context->validationError(GL_INVALID_OPERATION, kES31Required);
        return false;
    }

    return ValidateProgramUniform2uivBase(context, programPacked, locationPacked, count, value);
}

bool ValidateProgramUniform3f(const Context *context,
                              ShaderProgramID programPacked,
                              UniformLocation locationPacked,
                              GLfloat v0,
                              GLfloat v1,
                              GLfloat v2)
{
    if (context->getClientVersion() < ES_3_1)
    {
        context->validationError(GL_INVALID_OPERATION, kES31Required);
        return false;
    }

    return ValidateProgramUniform3fBase(context, programPacked, locationPacked, v0, v1, v2);
}

bool ValidateProgramUniform3fv(const Context *context,
                               ShaderProgramID programPacked,
                               UniformLocation locationPacked,
                               GLsizei count,
                               const GLfloat *value)
{
    if (context->getClientVersion() < ES_3_1)
    {
        context->validationError(GL_INVALID_OPERATION, kES31Required);
        return false;
    }

    return ValidateProgramUniform3fvBase(context, programPacked, locationPacked, count, value);
}

bool ValidateProgramUniform3i(const Context *context,
                              ShaderProgramID programPacked,
                              UniformLocation locationPacked,
                              GLint v0,
                              GLint v1,
                              GLint v2)
{
    if (context->getClientVersion() < ES_3_1)
    {
        context->validationError(GL_INVALID_OPERATION, kES31Required);
        return false;
    }

    return ValidateProgramUniform3iBase(context, programPacked, locationPacked, v0, v1, v2);
}

bool ValidateProgramUniform3iv(const Context *context,
                               ShaderProgramID programPacked,
                               UniformLocation locationPacked,
                               GLsizei count,
                               const GLint *value)
{
    if (context->getClientVersion() < ES_3_1)
    {
        context->validationError(GL_INVALID_OPERATION, kES31Required);
        return false;
    }

    return ValidateProgramUniform3ivBase(context, programPacked, locationPacked, count, value);
}

bool ValidateProgramUniform3ui(const Context *context,
                               ShaderProgramID programPacked,
                               UniformLocation locationPacked,
                               GLuint v0,
                               GLuint v1,
                               GLuint v2)
{
    if (context->getClientVersion() < ES_3_1)
    {
        context->validationError(GL_INVALID_OPERATION, kES31Required);
        return false;
    }

    return ValidateProgramUniform3uiBase(context, programPacked, locationPacked, v0, v1, v2);
}

bool ValidateProgramUniform3uiv(const Context *context,
                                ShaderProgramID programPacked,
                                UniformLocation locationPacked,
                                GLsizei count,
                                const GLuint *value)
{
    if (context->getClientVersion() < ES_3_1)
    {
        context->validationError(GL_INVALID_OPERATION, kES31Required);
        return false;
    }

    return ValidateProgramUniform3uivBase(context, programPacked, locationPacked, count, value);
}

bool ValidateProgramUniform4f(const Context *context,
                              ShaderProgramID programPacked,
                              UniformLocation locationPacked,
                              GLfloat v0,
                              GLfloat v1,
                              GLfloat v2,
                              GLfloat v3)
{
    if (context->getClientVersion() < ES_3_1)
    {
        context->validationError(GL_INVALID_OPERATION, kES31Required);
        return false;
    }

    return ValidateProgramUniform4fBase(context, programPacked, locationPacked, v0, v1, v2, v3);
}

bool ValidateProgramUniform4fv(const Context *context,
                               ShaderProgramID programPacked,
                               UniformLocation locationPacked,
                               GLsizei count,
                               const GLfloat *value)
{
    if (context->getClientVersion() < ES_3_1)
    {
        context->validationError(GL_INVALID_OPERATION, kES31Required);
        return false;
    }

    return ValidateProgramUniform4fvBase(context, programPacked, locationPacked, count, value);
}

bool ValidateProgramUniform4i(const Context *context,
                              ShaderProgramID programPacked,
                              UniformLocation locationPacked,
                              GLint v0,
                              GLint v1,
                              GLint v2,
                              GLint v3)
{
    if (context->getClientVersion() < ES_3_1)
    {
        context->validationError(GL_INVALID_OPERATION, kES31Required);
        return false;
    }

    return ValidateProgramUniform4iBase(context, programPacked, locationPacked, v0, v1, v2, v3);
}

bool ValidateProgramUniform4iv(const Context *context,
                               ShaderProgramID programPacked,
                               UniformLocation locationPacked,
                               GLsizei count,
                               const GLint *value)
{
    if (context->getClientVersion() < ES_3_1)
    {
        context->validationError(GL_INVALID_OPERATION, kES31Required);
        return false;
    }

    return ValidateProgramUniform4ivBase(context, programPacked, locationPacked, count, value);
}

bool ValidateProgramUniform4ui(const Context *context,
                               ShaderProgramID programPacked,
                               UniformLocation locationPacked,
                               GLuint v0,
                               GLuint v1,
                               GLuint v2,
                               GLuint v3)
{
    if (context->getClientVersion() < ES_3_1)
    {
        context->validationError(GL_INVALID_OPERATION, kES31Required);
        return false;
    }

    return ValidateProgramUniform4uiBase(context, programPacked, locationPacked, v0, v1, v2, v3);
}

bool ValidateProgramUniform4uiv(const Context *context,
                                ShaderProgramID programPacked,
                                UniformLocation locationPacked,
                                GLsizei count,
                                const GLuint *value)
{
    if (context->getClientVersion() < ES_3_1)
    {
        context->validationError(GL_INVALID_OPERATION, kES31Required);
        return false;
    }

    return ValidateProgramUniform4uivBase(context, programPacked, locationPacked, count, value);
}

bool ValidateProgramUniformMatrix2fv(const Context *context,
                                     ShaderProgramID programPacked,
                                     UniformLocation locationPacked,
                                     GLsizei count,
                                     GLboolean transpose,
                                     const GLfloat *value)
{
    if (context->getClientVersion() < ES_3_1)
    {
        context->validationError(GL_INVALID_OPERATION, kES31Required);
        return false;
    }

    return ValidateProgramUniformMatrix2fvBase(context, programPacked, locationPacked, count,
                                               transpose, value);
}

bool ValidateProgramUniformMatrix2x3fv(const Context *context,
                                       ShaderProgramID programPacked,
                                       UniformLocation locationPacked,
                                       GLsizei count,
                                       GLboolean transpose,
                                       const GLfloat *value)
{
    if (context->getClientVersion() < ES_3_1)
    {
        context->validationError(GL_INVALID_OPERATION, kES31Required);
        return false;
    }

    return ValidateProgramUniformMatrix2x3fvBase(context, programPacked, locationPacked, count,
                                                 transpose, value);
}

bool ValidateProgramUniformMatrix2x4fv(const Context *context,
                                       ShaderProgramID programPacked,
                                       UniformLocation locationPacked,
                                       GLsizei count,
                                       GLboolean transpose,
                                       const GLfloat *value)
{
    if (context->getClientVersion() < ES_3_1)
    {
        context->validationError(GL_INVALID_OPERATION, kES31Required);
        return false;
    }

    return ValidateProgramUniformMatrix2x4fvBase(context, programPacked, locationPacked, count,
                                                 transpose, value);
}

bool ValidateProgramUniformMatrix3fv(const Context *context,
                                     ShaderProgramID programPacked,
                                     UniformLocation locationPacked,
                                     GLsizei count,
                                     GLboolean transpose,
                                     const GLfloat *value)
{
    if (context->getClientVersion() < ES_3_1)
    {
        context->validationError(GL_INVALID_OPERATION, kES31Required);
        return false;
    }

    return ValidateProgramUniformMatrix3fvBase(context, programPacked, locationPacked, count,
                                               transpose, value);
}

bool ValidateProgramUniformMatrix3x2fv(const Context *context,
                                       ShaderProgramID programPacked,
                                       UniformLocation locationPacked,
                                       GLsizei count,
                                       GLboolean transpose,
                                       const GLfloat *value)
{
    if (context->getClientVersion() < ES_3_1)
    {
        context->validationError(GL_INVALID_OPERATION, kES31Required);
        return false;
    }

    return ValidateProgramUniformMatrix3x2fvBase(context, programPacked, locationPacked, count,
                                                 transpose, value);
}

bool ValidateProgramUniformMatrix3x4fv(const Context *context,
                                       ShaderProgramID programPacked,
                                       UniformLocation locationPacked,
                                       GLsizei count,
                                       GLboolean transpose,
                                       const GLfloat *value)
{
    if (context->getClientVersion() < ES_3_1)
    {
        context->validationError(GL_INVALID_OPERATION, kES31Required);
        return false;
    }

    return ValidateProgramUniformMatrix3x4fvBase(context, programPacked, locationPacked, count,
                                                 transpose, value);
}

bool ValidateProgramUniformMatrix4fv(const Context *context,
                                     ShaderProgramID programPacked,
                                     UniformLocation locationPacked,
                                     GLsizei count,
                                     GLboolean transpose,
                                     const GLfloat *value)
{
    if (context->getClientVersion() < ES_3_1)
    {
        context->validationError(GL_INVALID_OPERATION, kES31Required);
        return false;
    }

    return ValidateProgramUniformMatrix4fvBase(context, programPacked, locationPacked, count,
                                               transpose, value);
}

bool ValidateProgramUniformMatrix4x2fv(const Context *context,
                                       ShaderProgramID programPacked,
                                       UniformLocation locationPacked,
                                       GLsizei count,
                                       GLboolean transpose,
                                       const GLfloat *value)
{
    if (context->getClientVersion() < ES_3_1)
    {
        context->validationError(GL_INVALID_OPERATION, kES31Required);
        return false;
    }

    return ValidateProgramUniformMatrix4x2fvBase(context, programPacked, locationPacked, count,
                                                 transpose, value);
}

bool ValidateProgramUniformMatrix4x3fv(const Context *context,
                                       ShaderProgramID programPacked,
                                       UniformLocation locationPacked,
                                       GLsizei count,
                                       GLboolean transpose,
                                       const GLfloat *value)
{
    if (context->getClientVersion() < ES_3_1)
    {
        context->validationError(GL_INVALID_OPERATION, kES31Required);
        return false;
    }

    return ValidateProgramUniformMatrix4x3fvBase(context, programPacked, locationPacked, count,
                                                 transpose, value);
}

bool ValidateUseProgramStages(const Context *context,
                              ProgramPipelineID pipelinePacked,
                              GLbitfield stages,
                              ShaderProgramID programPacked)
{
    if (context->getClientVersion() < ES_3_1)
    {
        context->validationError(GL_INVALID_OPERATION, kES31Required);
        return false;
    }

    return ValidateUseProgramStagesBase(context, pipelinePacked, stages, programPacked);
}

bool ValidateValidateProgramPipeline(const Context *context, ProgramPipelineID pipelinePacked)
{
    if (context->getClientVersion() < ES_3_1)
    {
        context->validationError(GL_INVALID_OPERATION, kES31Required);
        return false;
    }

    return ValidateValidateProgramPipelineBase(context, pipelinePacked);
}

bool ValidateMemoryBarrier(const Context *context, GLbitfield barriers)
{
    if (context->getClientVersion() < ES_3_1)
    {
        context->validationError(GL_INVALID_OPERATION, kES31Required);
        return false;
    }

    if (barriers == GL_ALL_BARRIER_BITS)
    {
        return true;
    }

    GLbitfield supported_barrier_bits =
        GL_VERTEX_ATTRIB_ARRAY_BARRIER_BIT | GL_ELEMENT_ARRAY_BARRIER_BIT | GL_UNIFORM_BARRIER_BIT |
        GL_TEXTURE_FETCH_BARRIER_BIT | GL_SHADER_IMAGE_ACCESS_BARRIER_BIT | GL_COMMAND_BARRIER_BIT |
        GL_PIXEL_BUFFER_BARRIER_BIT | GL_TEXTURE_UPDATE_BARRIER_BIT | GL_BUFFER_UPDATE_BARRIER_BIT |
        GL_FRAMEBUFFER_BARRIER_BIT | GL_TRANSFORM_FEEDBACK_BARRIER_BIT |
        GL_ATOMIC_COUNTER_BARRIER_BIT | GL_SHADER_STORAGE_BARRIER_BIT;

    if (context->getExtensions().bufferStorageEXT)
    {
        supported_barrier_bits |= GL_CLIENT_MAPPED_BUFFER_BARRIER_BIT_EXT;
    }

    if (barriers == 0 || (barriers & ~supported_barrier_bits) != 0)
    {
        context->validationError(GL_INVALID_VALUE, kInvalidMemoryBarrierBit);
        return false;
    }

    return true;
}

bool ValidateMemoryBarrierByRegion(const Context *context, GLbitfield barriers)
{
    if (context->getClientVersion() < ES_3_1)
    {
        context->validationError(GL_INVALID_OPERATION, kES31Required);
        return false;
    }

    if (barriers == GL_ALL_BARRIER_BITS)
    {
        return true;
    }

    GLbitfield supported_barrier_bits = GL_ATOMIC_COUNTER_BARRIER_BIT | GL_FRAMEBUFFER_BARRIER_BIT |
                                        GL_SHADER_IMAGE_ACCESS_BARRIER_BIT |
                                        GL_SHADER_STORAGE_BARRIER_BIT |
                                        GL_TEXTURE_FETCH_BARRIER_BIT | GL_UNIFORM_BARRIER_BIT;
    if (barriers == 0 || (barriers & ~supported_barrier_bits) != 0)
    {
        context->validationError(GL_INVALID_VALUE, kInvalidMemoryBarrierBit);
        return false;
    }

    return true;
}

bool ValidateSampleMaski(const Context *context, GLuint maskNumber, GLbitfield mask)
{
    if (context->getClientVersion() < ES_3_1)
    {
        context->validationError(GL_INVALID_OPERATION, kES31Required);
        return false;
    }

    return ValidateSampleMaskiBase(context, maskNumber, mask);
}

bool ValidateMinSampleShadingOES(const Context *context, GLfloat value)
{
    if (!context->getExtensions().sampleShadingOES)
    {
        context->validationError(GL_INVALID_OPERATION, kExtensionNotEnabled);
        return false;
    }

    return true;
}

bool ValidateFramebufferTextureEXT(const Context *context,
                                   GLenum target,
                                   GLenum attachment,
                                   TextureID texture,
                                   GLint level)
{
    if (!context->getExtensions().geometryShader)
    {
        context->validationError(GL_INVALID_OPERATION, kGeometryShaderExtensionNotEnabled);
        return false;
    }

    if (texture.value != 0)
    {
        Texture *tex = context->getTexture(texture);

        // [EXT_geometry_shader] Section 9.2.8 "Attaching Texture Images to a Framebuffer"
        // An INVALID_VALUE error is generated if <texture> is not the name of a texture object.
        // We put this validation before ValidateFramebufferTextureBase because it is an
        // INVALID_OPERATION error for both FramebufferTexture2D and FramebufferTextureLayer:
        // [OpenGL ES 3.1] Chapter 9.2.8 (FramebufferTexture2D)
        // An INVALID_OPERATION error is generated if texture is not zero, and does not name an
        // existing texture object of type matching textarget.
        // [OpenGL ES 3.1 Chapter 9.2.8 (FramebufferTextureLayer)
        // An INVALID_OPERATION error is generated if texture is non-zero and is not the name of a
        // three-dimensional or two-dimensional array texture.
        if (tex == nullptr)
        {
            context->validationError(GL_INVALID_VALUE, kInvalidTextureName);
            return false;
        }

        if (!ValidMipLevel(context, tex->getType(), level))
        {
            context->validationError(GL_INVALID_VALUE, kInvalidMipLevel);
            return false;
        }
    }

    if (!ValidateFramebufferTextureBase(context, target, attachment, texture, level))
    {
        return false;
    }

    return true;
}

// GL_OES_texture_storage_multisample_2d_array
bool ValidateTexStorage3DMultisampleOES(const Context *context,
                                        TextureType target,
                                        GLsizei samples,
                                        GLenum sizedinternalformat,
                                        GLsizei width,
                                        GLsizei height,
                                        GLsizei depth,
                                        GLboolean fixedsamplelocations)
{
    if (!context->getExtensions().textureStorageMultisample2DArrayOES)
    {
        context->validationError(GL_INVALID_ENUM, kMultisampleArrayExtensionRequired);
        return false;
    }

    if (target != TextureType::_2DMultisampleArray)
    {
        context->validationError(GL_INVALID_ENUM, kTargetMustBeTexture2DMultisampleArrayOES);
        return false;
    }

    if (width < 1 || height < 1 || depth < 1)
    {
        context->validationError(GL_INVALID_VALUE, kNegativeSize);
        return false;
    }

    if (depth > context->getCaps().maxArrayTextureLayers)
    {
        context->validationError(GL_INVALID_VALUE, kTextureDepthOutOfRange);
        return false;
    }

    return ValidateTexStorageMultisample(context, target, samples, sizedinternalformat, width,
                                         height);
}

bool ValidateTexStorageMem3DMultisampleEXT(const Context *context,
                                           TextureType target,
                                           GLsizei samples,
                                           GLenum internalFormat,
                                           GLsizei width,
                                           GLsizei height,
                                           GLsizei depth,
                                           GLboolean fixedSampleLocations,
                                           MemoryObjectID memory,
                                           GLuint64 offset)
{
    if (!context->getExtensions().memoryObject)
    {
        context->validationError(GL_INVALID_OPERATION, kExtensionNotEnabled);
        return false;
    }

    UNIMPLEMENTED();
    return false;
}

bool ValidateGetProgramResourceLocationIndexEXT(const Context *context,
                                                ShaderProgramID program,
                                                GLenum programInterface,
                                                const char *name)
{
    if (!context->getExtensions().blendFuncExtended)
    {
        context->validationError(GL_INVALID_OPERATION, kExtensionNotEnabled);
        return false;
    }
    if (context->getClientVersion() < ES_3_1)
    {
        context->validationError(GL_INVALID_OPERATION, kES31Required);
        return false;
    }
    if (programInterface != GL_PROGRAM_OUTPUT)
    {
        context->validationError(GL_INVALID_ENUM, kProgramInterfaceMustBeProgramOutput);
        return false;
    }
    Program *programObject = GetValidProgram(context, program);
    if (!programObject)
    {
        return false;
    }
    if (!programObject->isLinked())
    {
        context->validationError(GL_INVALID_OPERATION, kProgramNotLinked);
        return false;
    }
    return true;
}

// GL_OES_texture_buffer
bool ValidateTexBufferOES(const Context *context,
                          TextureType target,
                          GLenum internalformat,
                          BufferID bufferPacked)
{
    if (!context->getExtensions().textureBufferOES)
    {
        context->validationError(GL_INVALID_OPERATION, kTextureBufferExtensionNotAvailable);
        return false;
    }

    return ValidateTexBufferBase(context, target, internalformat, bufferPacked);
}

bool ValidateTexBufferRangeOES(const Context *context,
                               TextureType target,
                               GLenum internalformat,
                               BufferID bufferPacked,
                               GLintptr offset,
                               GLsizeiptr size)
{
    if (!context->getExtensions().textureBufferOES)
    {
        context->validationError(GL_INVALID_OPERATION, kTextureBufferExtensionNotAvailable);
        return false;
    }

    return ValidateTexBufferRangeBase(context, target, internalformat, bufferPacked, offset, size);
}

// GL_EXT_texture_buffer
bool ValidateTexBufferEXT(const Context *context,
                          TextureType target,
                          GLenum internalformat,
                          BufferID bufferPacked)
{
    if (!context->getExtensions().textureBufferEXT)
    {
        context->validationError(GL_INVALID_OPERATION, kTextureBufferExtensionNotAvailable);
        return false;
    }

    return ValidateTexBufferBase(context, target, internalformat, bufferPacked);
}

bool ValidateTexBufferRangeEXT(const Context *context,
                               TextureType target,
                               GLenum internalformat,
                               BufferID bufferPacked,
                               GLintptr offset,
                               GLsizeiptr size)
{
    if (!context->getExtensions().textureBufferEXT)
    {
        context->validationError(GL_INVALID_OPERATION, kTextureBufferExtensionNotAvailable);
        return false;
    }

    return ValidateTexBufferRangeBase(context, target, internalformat, bufferPacked, offset, size);
}

bool ValidateTexBufferBase(const Context *context,
                           TextureType target,
                           GLenum internalformat,
                           BufferID bufferPacked)
{
    if (target != TextureType::Buffer)
    {
        context->validationError(GL_INVALID_ENUM, kTextureBufferTarget);
        return false;
    }

    switch (internalformat)
    {
        case GL_R8:
        case GL_R16F:
        case GL_R32F:
        case GL_R8I:
        case GL_R16I:
        case GL_R32I:
        case GL_R8UI:
        case GL_R16UI:
        case GL_R32UI:
        case GL_RG8:
        case GL_RG16F:
        case GL_RG32F:
        case GL_RG8I:
        case GL_RG16I:
        case GL_RG32I:
        case GL_RG8UI:
        case GL_RG16UI:
        case GL_RG32UI:
        case GL_RGB32F:
        case GL_RGB32I:
        case GL_RGB32UI:
        case GL_RGBA8:
        case GL_RGBA16F:
        case GL_RGBA32F:
        case GL_RGBA8I:
        case GL_RGBA16I:
        case GL_RGBA32I:
        case GL_RGBA8UI:
        case GL_RGBA16UI:
        case GL_RGBA32UI:
            break;

        default:
            context->validationError(GL_INVALID_ENUM, kTextureBufferInternalFormat);
            return false;
    }

    if (bufferPacked.value != 0)
    {
        if (!context->isBufferGenerated(bufferPacked))
        {
            context->validationError(GL_INVALID_OPERATION, kTextureBufferInvalidBuffer);
            return false;
        }
    }

    return true;
}

bool ValidateTexBufferRangeBase(const Context *context,
                                TextureType target,
                                GLenum internalformat,
                                BufferID bufferPacked,
                                GLintptr offset,
                                GLsizeiptr size)
{
    const Caps &caps = context->getCaps();

    if (offset < 0 || (offset % caps.textureBufferOffsetAlignment) != 0)
    {
        context->validationError(GL_INVALID_VALUE, kTextureBufferOffsetAlignment);
        return false;
    }
    if (size <= 0)
    {
        context->validationError(GL_INVALID_VALUE, kTextureBufferSize);
        return false;
    }
    const Buffer *buffer = context->getBuffer(bufferPacked);

    if (!buffer)
    {
        context->validationError(GL_INVALID_OPERATION, kBufferNotBound);
        return false;
    }

    if (offset + size > buffer->getSize())
    {
        context->validationError(GL_INVALID_VALUE, kTextureBufferSizeOffset);
        return false;
    }

    return ValidateTexBufferBase(context, target, internalformat, bufferPacked);
}

}  // namespace gl
