//
// Copyright (c) 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.
//

// validationEGL.cpp: Validation functions for generic EGL entry point parameters

#include "libANGLE/validationEGL.h"

#include "common/utilities.h"
#include "libANGLE/Config.h"
#include "libANGLE/Context.h"
#include "libANGLE/Device.h"
#include "libANGLE/Display.h"
#include "libANGLE/Image.h"
#include "libANGLE/Stream.h"
#include "libANGLE/Surface.h"

#include <EGL/eglext.h>

namespace
{
size_t GetMaximumMipLevel(const gl::Context *context, GLenum target)
{
    const gl::Caps &caps = context->getCaps();

    size_t maxDimension = 0;
    switch (target)
    {
        case GL_TEXTURE_2D:
            maxDimension = caps.max2DTextureSize;
            break;
        case GL_TEXTURE_CUBE_MAP:
            maxDimension = caps.maxCubeMapTextureSize;
            break;
        case GL_TEXTURE_3D:
            maxDimension = caps.max3DTextureSize;
            break;
        case GL_TEXTURE_2D_ARRAY:
            maxDimension = caps.max2DTextureSize;
            break;
        default:
            UNREACHABLE();
    }

    return gl::log2(static_cast<int>(maxDimension));
}

bool TextureHasNonZeroMipLevelsSpecified(const gl::Context *context, const gl::Texture *texture)
{
    size_t maxMip = GetMaximumMipLevel(context, texture->getTarget());
    for (size_t level = 1; level < maxMip; level++)
    {
        if (texture->getTarget() == GL_TEXTURE_CUBE_MAP)
        {
            for (GLenum face = gl::FirstCubeMapTextureTarget; face <= gl::LastCubeMapTextureTarget;
                 face++)
            {
                if (texture->getInternalFormat(face, level) != GL_NONE)
                {
                    return true;
                }
            }
        }
        else
        {
            if (texture->getInternalFormat(texture->getTarget(), level) != GL_NONE)
            {
                return true;
            }
        }
    }

    return false;
}

bool CubeTextureHasUnspecifiedLevel0Face(const gl::Texture *texture)
{
    ASSERT(texture->getTarget() == GL_TEXTURE_CUBE_MAP);
    for (GLenum face = gl::FirstCubeMapTextureTarget; face <= gl::LastCubeMapTextureTarget; face++)
    {
        if (texture->getInternalFormat(face, 0) == GL_NONE)
        {
            return true;
        }
    }

    return false;
}

egl::Error ValidateStreamAttribute(const EGLAttrib attribute,
                                   const EGLAttrib value,
                                   const egl::DisplayExtensions &extensions)
{
    switch (attribute)
    {
        case EGL_STREAM_STATE_KHR:
        case EGL_PRODUCER_FRAME_KHR:
        case EGL_CONSUMER_FRAME_KHR:
            return egl::Error(EGL_BAD_ACCESS, "Attempt to initialize readonly parameter");
        case EGL_CONSUMER_LATENCY_USEC_KHR:
            // Technically not in spec but a latency < 0 makes no sense so we check it
            if (value < 0)
            {
                return egl::Error(EGL_BAD_PARAMETER, "Latency must be positive");
            }
            break;
        case EGL_CONSUMER_ACQUIRE_TIMEOUT_USEC_KHR:
            if (!extensions.streamConsumerGLTexture)
            {
                return egl::Error(EGL_BAD_ATTRIBUTE, "Consumer GL extension not enabled");
            }
            // Again not in spec but it should be positive anyways
            if (value < 0)
            {
                return egl::Error(EGL_BAD_PARAMETER, "Timeout must be positive");
            }
            break;
        default:
            return egl::Error(EGL_BAD_ATTRIBUTE, "Invalid stream attribute");
    }
    return egl::Error(EGL_SUCCESS);
}
}  // namespace

namespace egl
{

Error ValidateDisplay(const Display *display)
{
    if (display == EGL_NO_DISPLAY)
    {
        return Error(EGL_BAD_DISPLAY, "display is EGL_NO_DISPLAY.");
    }

    if (!Display::isValidDisplay(display))
    {
        return Error(EGL_BAD_DISPLAY, "display is not a valid display.");
    }

    if (!display->isInitialized())
    {
        return Error(EGL_NOT_INITIALIZED, "display is not initialized.");
    }

    return Error(EGL_SUCCESS);
}

Error ValidateSurface(const Display *display, Surface *surface)
{
    Error error = ValidateDisplay(display);
    if (error.isError())
    {
        return error;
    }

    if (!display->isValidSurface(surface))
    {
        return Error(EGL_BAD_SURFACE);
    }

    return Error(EGL_SUCCESS);
}

Error ValidateConfig(const Display *display, const Config *config)
{
    Error error = ValidateDisplay(display);
    if (error.isError())
    {
        return error;
    }

    if (!display->isValidConfig(config))
    {
        return Error(EGL_BAD_CONFIG);
    }

    return Error(EGL_SUCCESS);
}

Error ValidateContext(const Display *display, gl::Context *context)
{
    Error error = ValidateDisplay(display);
    if (error.isError())
    {
        return error;
    }

    if (!display->isValidContext(context))
    {
        return Error(EGL_BAD_CONTEXT);
    }

    return Error(EGL_SUCCESS);
}

Error ValidateImage(const Display *display, const Image *image)
{
    Error error = ValidateDisplay(display);
    if (error.isError())
    {
        return error;
    }

    if (!display->isValidImage(image))
    {
        return Error(EGL_BAD_PARAMETER, "image is not valid.");
    }

    return Error(EGL_SUCCESS);
}

Error ValidateStream(const Display *display, const Stream *stream)
{
    Error error = ValidateDisplay(display);
    if (error.isError())
    {
        return error;
    }

    const DisplayExtensions &displayExtensions = display->getExtensions();
    if (!displayExtensions.stream)
    {
        return Error(EGL_BAD_ACCESS, "Stream extension not active");
    }

    if (stream == EGL_NO_STREAM_KHR || !display->isValidStream(stream))
    {
        return Error(EGL_BAD_STREAM_KHR, "Invalid stream");
    }

    return Error(EGL_SUCCESS);
}

Error ValidateCreateContext(Display *display, Config *configuration, gl::Context *shareContext,
                            const AttributeMap& attributes)
{
    Error error = ValidateConfig(display, configuration);
    if (error.isError())
    {
        return error;
    }

    // Get the requested client version (default is 1) and check it is 2 or 3.
    EGLAttrib clientMajorVersion = 1;
    EGLAttrib clientMinorVersion = 0;
    EGLAttrib contextFlags       = 0;
    bool resetNotification = false;
    bool robustAccess = false;
    for (AttributeMap::const_iterator attributeIter = attributes.begin(); attributeIter != attributes.end(); attributeIter++)
    {
        EGLAttrib attribute = attributeIter->first;
        EGLAttrib value     = attributeIter->second;

        switch (attribute)
        {
          case EGL_CONTEXT_CLIENT_VERSION:
            clientMajorVersion = value;
            break;

          case EGL_CONTEXT_MINOR_VERSION:
            clientMinorVersion = value;
            break;

          case EGL_CONTEXT_FLAGS_KHR:
            contextFlags = value;
            break;

          case EGL_CONTEXT_OPENGL_DEBUG:
              break;

          case EGL_CONTEXT_OPENGL_PROFILE_MASK_KHR:
            // Only valid for OpenGL (non-ES) contexts
            return Error(EGL_BAD_ATTRIBUTE);

          case EGL_CONTEXT_OPENGL_ROBUST_ACCESS_EXT:
            if (!display->getExtensions().createContextRobustness)
            {
                return Error(EGL_BAD_ATTRIBUTE);
            }
            if (value != EGL_TRUE && value != EGL_FALSE)
            {
                return Error(EGL_BAD_ATTRIBUTE);
            }
            robustAccess = (value == EGL_TRUE);
            break;

          case EGL_CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY_KHR:
            static_assert(EGL_LOSE_CONTEXT_ON_RESET_EXT == EGL_LOSE_CONTEXT_ON_RESET_KHR, "EGL extension enums not equal.");
            static_assert(EGL_NO_RESET_NOTIFICATION_EXT == EGL_NO_RESET_NOTIFICATION_KHR, "EGL extension enums not equal.");
            // same as EGL_CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY_EXT, fall through
          case EGL_CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY_EXT:
            if (!display->getExtensions().createContextRobustness)
            {
                return Error(EGL_BAD_ATTRIBUTE);
            }
            if (value == EGL_LOSE_CONTEXT_ON_RESET_EXT)
            {
                resetNotification = true;
            }
            else if (value != EGL_NO_RESET_NOTIFICATION_EXT)
            {
                return Error(EGL_BAD_ATTRIBUTE);
            }
            break;

          case EGL_CONTEXT_OPENGL_NO_ERROR_KHR:
              if (!display->getExtensions().createContextNoError)
              {
                  return Error(EGL_BAD_ATTRIBUTE, "Invalid Context attribute.");
              }
              if (value != EGL_TRUE && value != EGL_FALSE)
              {
                  return Error(EGL_BAD_ATTRIBUTE, "Attribute must be EGL_TRUE or EGL_FALSE.");
              }
              break;

          default:
            return Error(EGL_BAD_ATTRIBUTE);
        }
    }

    if ((clientMajorVersion != 2 && clientMajorVersion != 3) || clientMinorVersion != 0)
    {
        return Error(EGL_BAD_CONFIG);
    }

    if (clientMajorVersion == 3 && !(configuration->conformant & EGL_OPENGL_ES3_BIT_KHR))
    {
        return Error(EGL_BAD_CONFIG);
    }

    // Note: EGL_CONTEXT_OPENGL_FORWARD_COMPATIBLE_BIT_KHR does not apply to ES
    const EGLint validContextFlags = (EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR |
                                      EGL_CONTEXT_OPENGL_ROBUST_ACCESS_BIT_KHR);
    if ((contextFlags & ~validContextFlags) != 0)
    {
        return Error(EGL_BAD_ATTRIBUTE);
    }

    if ((contextFlags & EGL_CONTEXT_OPENGL_ROBUST_ACCESS_BIT_KHR) > 0)
    {
        robustAccess = true;
    }

    if (robustAccess)
    {
        // Unimplemented
        return Error(EGL_BAD_CONFIG);
    }

    if (shareContext)
    {
        // Shared context is invalid or is owned by another display
        if (!display->isValidContext(shareContext))
        {
            return Error(EGL_BAD_MATCH);
        }

        if (shareContext->isResetNotificationEnabled() != resetNotification)
        {
            return Error(EGL_BAD_MATCH);
        }

        if (shareContext->getClientVersion() != clientMajorVersion)
        {
            return Error(EGL_BAD_CONTEXT);
        }
    }

    return Error(EGL_SUCCESS);
}

Error ValidateCreateWindowSurface(Display *display, Config *config, EGLNativeWindowType window,
                                  const AttributeMap& attributes)
{
    Error error = ValidateConfig(display, config);
    if (error.isError())
    {
        return error;
    }

    if (!display->isValidNativeWindow(window))
    {
        return Error(EGL_BAD_NATIVE_WINDOW);
    }

    const DisplayExtensions &displayExtensions = display->getExtensions();

    for (AttributeMap::const_iterator attributeIter = attributes.begin(); attributeIter != attributes.end(); attributeIter++)
    {
        EGLAttrib attribute = attributeIter->first;
        EGLAttrib value     = attributeIter->second;

        switch (attribute)
        {
          case EGL_RENDER_BUFFER:
            switch (value)
            {
              case EGL_BACK_BUFFER:
                break;
              case EGL_SINGLE_BUFFER:
                return Error(EGL_BAD_MATCH);   // Rendering directly to front buffer not supported
              default:
                return Error(EGL_BAD_ATTRIBUTE);
            }
            break;

          case EGL_POST_SUB_BUFFER_SUPPORTED_NV:
            if (!displayExtensions.postSubBuffer)
            {
                return Error(EGL_BAD_ATTRIBUTE);
            }
            break;

          case EGL_FLEXIBLE_SURFACE_COMPATIBILITY_SUPPORTED_ANGLE:
              if (!displayExtensions.flexibleSurfaceCompatibility)
              {
                  return Error(EGL_BAD_ATTRIBUTE);
              }
              break;

          case EGL_WIDTH:
          case EGL_HEIGHT:
            if (!displayExtensions.windowFixedSize)
            {
                return Error(EGL_BAD_ATTRIBUTE);
            }
            if (value < 0)
            {
                return Error(EGL_BAD_PARAMETER);
            }
            break;

          case EGL_FIXED_SIZE_ANGLE:
            if (!displayExtensions.windowFixedSize)
            {
                return Error(EGL_BAD_ATTRIBUTE);
            }
            break;

          case EGL_SURFACE_ORIENTATION_ANGLE:
              if (!displayExtensions.surfaceOrientation)
              {
                  return Error(EGL_BAD_ATTRIBUTE, "EGL_ANGLE_surface_orientation is not enabled.");
              }
              break;

          case EGL_VG_COLORSPACE:
            return Error(EGL_BAD_MATCH);

          case EGL_VG_ALPHA_FORMAT:
            return Error(EGL_BAD_MATCH);

          case EGL_DIRECT_COMPOSITION_ANGLE:
              if (!displayExtensions.directComposition)
              {
                  return Error(EGL_BAD_ATTRIBUTE);
              }
              break;

          default:
            return Error(EGL_BAD_ATTRIBUTE);
        }
    }

    if (Display::hasExistingWindowSurface(window))
    {
        return Error(EGL_BAD_ALLOC);
    }

    return Error(EGL_SUCCESS);
}

Error ValidateCreatePbufferSurface(Display *display, Config *config, const AttributeMap& attributes)
{
    Error error = ValidateConfig(display, config);
    if (error.isError())
    {
        return error;
    }

    const DisplayExtensions &displayExtensions = display->getExtensions();

    for (AttributeMap::const_iterator attributeIter = attributes.begin(); attributeIter != attributes.end(); attributeIter++)
    {
        EGLAttrib attribute = attributeIter->first;
        EGLAttrib value     = attributeIter->second;

        switch (attribute)
        {
          case EGL_WIDTH:
          case EGL_HEIGHT:
            if (value < 0)
            {
                return Error(EGL_BAD_PARAMETER);
            }
            break;

          case EGL_LARGEST_PBUFFER:
            break;

          case EGL_TEXTURE_FORMAT:
            switch (value)
            {
              case EGL_NO_TEXTURE:
              case EGL_TEXTURE_RGB:
              case EGL_TEXTURE_RGBA:
                break;
              default:
                return Error(EGL_BAD_ATTRIBUTE);
            }
            break;

          case EGL_TEXTURE_TARGET:
            switch (value)
            {
              case EGL_NO_TEXTURE:
              case EGL_TEXTURE_2D:
                break;
              default:
                return Error(EGL_BAD_ATTRIBUTE);
            }
            break;

          case EGL_MIPMAP_TEXTURE:
            break;

          case EGL_VG_COLORSPACE:
            break;

          case EGL_VG_ALPHA_FORMAT:
            break;

          case EGL_FLEXIBLE_SURFACE_COMPATIBILITY_SUPPORTED_ANGLE:
              if (!displayExtensions.flexibleSurfaceCompatibility)
              {
                  return Error(
                      EGL_BAD_ATTRIBUTE,
                      "EGL_FLEXIBLE_SURFACE_COMPATIBILITY_SUPPORTED_ANGLE cannot be used without "
                      "EGL_ANGLE_flexible_surface_compatibility support.");
              }
              break;

          default:
            return Error(EGL_BAD_ATTRIBUTE);
        }
    }

    if (!(config->surfaceType & EGL_PBUFFER_BIT))
    {
        return Error(EGL_BAD_MATCH);
    }

    const Caps &caps = display->getCaps();

    EGLAttrib textureFormat = attributes.get(EGL_TEXTURE_FORMAT, EGL_NO_TEXTURE);
    EGLAttrib textureTarget = attributes.get(EGL_TEXTURE_TARGET, EGL_NO_TEXTURE);

    if ((textureFormat != EGL_NO_TEXTURE && textureTarget == EGL_NO_TEXTURE) ||
        (textureFormat == EGL_NO_TEXTURE && textureTarget != EGL_NO_TEXTURE))
    {
        return Error(EGL_BAD_MATCH);
    }

    if ((textureFormat == EGL_TEXTURE_RGB  && config->bindToTextureRGB != EGL_TRUE) ||
        (textureFormat == EGL_TEXTURE_RGBA && config->bindToTextureRGBA != EGL_TRUE))
    {
        return Error(EGL_BAD_ATTRIBUTE);
    }

    EGLint width  = static_cast<EGLint>(attributes.get(EGL_WIDTH, 0));
    EGLint height = static_cast<EGLint>(attributes.get(EGL_HEIGHT, 0));
    if (textureFormat != EGL_NO_TEXTURE && !caps.textureNPOT && (!gl::isPow2(width) || !gl::isPow2(height)))
    {
        return Error(EGL_BAD_MATCH);
    }

    return Error(EGL_SUCCESS);
}

Error ValidateCreatePbufferFromClientBuffer(Display *display, EGLenum buftype, EGLClientBuffer buffer,
                                            Config *config, const AttributeMap& attributes)
{
    Error error = ValidateConfig(display, config);
    if (error.isError())
    {
        return error;
    }

    const DisplayExtensions &displayExtensions = display->getExtensions();

    switch (buftype)
    {
      case EGL_D3D_TEXTURE_2D_SHARE_HANDLE_ANGLE:
        if (!displayExtensions.d3dShareHandleClientBuffer)
        {
            return Error(EGL_BAD_PARAMETER);
        }
        if (buffer == nullptr)
        {
            return Error(EGL_BAD_PARAMETER);
        }
        break;

      default:
        return Error(EGL_BAD_PARAMETER);
    }

    for (AttributeMap::const_iterator attributeIter = attributes.begin(); attributeIter != attributes.end(); attributeIter++)
    {
        EGLAttrib attribute = attributeIter->first;
        EGLAttrib value     = attributeIter->second;

        switch (attribute)
        {
          case EGL_WIDTH:
          case EGL_HEIGHT:
            if (!displayExtensions.d3dShareHandleClientBuffer)
            {
                return Error(EGL_BAD_PARAMETER);
            }
            if (value < 0)
            {
                return Error(EGL_BAD_PARAMETER);
            }
            break;

          case EGL_TEXTURE_FORMAT:
            switch (value)
            {
              case EGL_NO_TEXTURE:
              case EGL_TEXTURE_RGB:
              case EGL_TEXTURE_RGBA:
                break;
              default:
                return Error(EGL_BAD_ATTRIBUTE);
            }
            break;

          case EGL_TEXTURE_TARGET:
            switch (value)
            {
              case EGL_NO_TEXTURE:
              case EGL_TEXTURE_2D:
                break;
              default:
                return Error(EGL_BAD_ATTRIBUTE);
            }
            break;

          case EGL_MIPMAP_TEXTURE:
            break;

          case EGL_FLEXIBLE_SURFACE_COMPATIBILITY_SUPPORTED_ANGLE:
              if (!displayExtensions.flexibleSurfaceCompatibility)
              {
                  return Error(
                      EGL_BAD_ATTRIBUTE,
                      "EGL_FLEXIBLE_SURFACE_COMPATIBILITY_SUPPORTED_ANGLE cannot be used without "
                      "EGL_ANGLE_flexible_surface_compatibility support.");
              }
              break;

          default:
            return Error(EGL_BAD_ATTRIBUTE);
        }
    }

    if (!(config->surfaceType & EGL_PBUFFER_BIT))
    {
        return Error(EGL_BAD_MATCH);
    }

    EGLAttrib textureFormat = attributes.get(EGL_TEXTURE_FORMAT, EGL_NO_TEXTURE);
    EGLAttrib textureTarget = attributes.get(EGL_TEXTURE_TARGET, EGL_NO_TEXTURE);
    if ((textureFormat != EGL_NO_TEXTURE && textureTarget == EGL_NO_TEXTURE) ||
        (textureFormat == EGL_NO_TEXTURE && textureTarget != EGL_NO_TEXTURE))
    {
        return Error(EGL_BAD_MATCH);
    }

    if ((textureFormat == EGL_TEXTURE_RGB  && config->bindToTextureRGB  != EGL_TRUE) ||
        (textureFormat == EGL_TEXTURE_RGBA && config->bindToTextureRGBA != EGL_TRUE))
    {
        return Error(EGL_BAD_ATTRIBUTE);
    }

    if (buftype == EGL_D3D_TEXTURE_2D_SHARE_HANDLE_ANGLE)
    {
        EGLint width  = static_cast<EGLint>(attributes.get(EGL_WIDTH, 0));
        EGLint height = static_cast<EGLint>(attributes.get(EGL_HEIGHT, 0));

        if (width == 0 || height == 0)
        {
            return Error(EGL_BAD_ATTRIBUTE);
        }

        const Caps &caps = display->getCaps();
        if (textureFormat != EGL_NO_TEXTURE && !caps.textureNPOT && (!gl::isPow2(width) || !gl::isPow2(height)))
        {
            return Error(EGL_BAD_MATCH);
        }
    }

    return Error(EGL_SUCCESS);
}

Error ValidateCompatibleConfigs(const Display *display,
                                const Config *config1,
                                const Surface *surface,
                                const Config *config2,
                                EGLint surfaceType)
{

    if (!surface->flexibleSurfaceCompatibilityRequested())
    {
        // Config compatibility is defined in section 2.2 of the EGL 1.5 spec

        bool colorBufferCompat = config1->colorBufferType == config2->colorBufferType;
        if (!colorBufferCompat)
        {
            return Error(EGL_BAD_MATCH, "Color buffer types are not compatible.");
        }

        bool colorCompat =
            config1->redSize == config2->redSize && config1->greenSize == config2->greenSize &&
            config1->blueSize == config2->blueSize && config1->alphaSize == config2->alphaSize &&
            config1->luminanceSize == config2->luminanceSize;
        if (!colorCompat)
        {
            return Error(EGL_BAD_MATCH, "Color buffer sizes are not compatible.");
        }

        bool dsCompat = config1->depthSize == config2->depthSize &&
                        config1->stencilSize == config2->stencilSize;
        if (!dsCompat)
        {
            return Error(EGL_BAD_MATCH, "Depth-stencil buffer types are not compatible.");
        }
    }

    bool surfaceTypeCompat = (config1->surfaceType & config2->surfaceType & surfaceType) != 0;
    if (!surfaceTypeCompat)
    {
        return Error(EGL_BAD_MATCH, "Surface types are not compatible.");
    }

    return Error(EGL_SUCCESS);
}

Error ValidateCreateImageKHR(const Display *display,
                             gl::Context *context,
                             EGLenum target,
                             EGLClientBuffer buffer,
                             const AttributeMap &attributes)
{
    Error error = ValidateContext(display, context);
    if (error.isError())
    {
        return error;
    }

    const DisplayExtensions &displayExtensions = display->getExtensions();

    if (!displayExtensions.imageBase && !displayExtensions.image)
    {
        // It is out of spec what happens when calling an extension function when the extension is
        // not available.
        // EGL_BAD_DISPLAY seems like a reasonable error.
        return Error(EGL_BAD_DISPLAY, "EGL_KHR_image not supported.");
    }

    // TODO(geofflang): Complete validation from EGL_KHR_image_base:
    // If the resource specified by <dpy>, <ctx>, <target>, <buffer> and <attrib_list> is itself an
    // EGLImage sibling, the error EGL_BAD_ACCESS is generated.

    for (AttributeMap::const_iterator attributeIter = attributes.begin();
         attributeIter != attributes.end(); attributeIter++)
    {
        EGLAttrib attribute = attributeIter->first;
        EGLAttrib value     = attributeIter->second;

        switch (attribute)
        {
            case EGL_IMAGE_PRESERVED_KHR:
                switch (value)
                {
                    case EGL_TRUE:
                    case EGL_FALSE:
                        break;

                    default:
                        return Error(EGL_BAD_PARAMETER,
                                     "EGL_IMAGE_PRESERVED_KHR must be EGL_TRUE or EGL_FALSE.");
                }
                break;

            case EGL_GL_TEXTURE_LEVEL_KHR:
                if (!displayExtensions.glTexture2DImage &&
                    !displayExtensions.glTextureCubemapImage && !displayExtensions.glTexture3DImage)
                {
                    return Error(EGL_BAD_PARAMETER,
                                 "EGL_GL_TEXTURE_LEVEL_KHR cannot be used without "
                                 "KHR_gl_texture_*_image support.");
                }

                if (value < 0)
                {
                    return Error(EGL_BAD_PARAMETER, "EGL_GL_TEXTURE_LEVEL_KHR cannot be negative.");
                }
                break;

            case EGL_GL_TEXTURE_ZOFFSET_KHR:
                if (!displayExtensions.glTexture3DImage)
                {
                    return Error(EGL_BAD_PARAMETER,
                                 "EGL_GL_TEXTURE_ZOFFSET_KHR cannot be used without "
                                 "KHR_gl_texture_3D_image support.");
                }
                break;

            default:
                return Error(EGL_BAD_PARAMETER, "invalid attribute: 0x%X", attribute);
        }
    }

    switch (target)
    {
        case EGL_GL_TEXTURE_2D_KHR:
        {
            if (!displayExtensions.glTexture2DImage)
            {
                return Error(EGL_BAD_PARAMETER, "KHR_gl_texture_2D_image not supported.");
            }

            if (buffer == 0)
            {
                return Error(EGL_BAD_PARAMETER,
                             "buffer cannot reference a 2D texture with the name 0.");
            }

            const gl::Texture *texture =
                context->getTexture(egl_gl::EGLClientBufferToGLObjectHandle(buffer));
            if (texture == nullptr || texture->getTarget() != GL_TEXTURE_2D)
            {
                return Error(EGL_BAD_PARAMETER, "target is not a 2D texture.");
            }

            if (texture->getBoundSurface() != nullptr)
            {
                return Error(EGL_BAD_ACCESS, "texture has a surface bound to it.");
            }

            EGLAttrib level = attributes.get(EGL_GL_TEXTURE_LEVEL_KHR, 0);
            if (texture->getWidth(GL_TEXTURE_2D, static_cast<size_t>(level)) == 0 ||
                texture->getHeight(GL_TEXTURE_2D, static_cast<size_t>(level)) == 0)
            {
                return Error(EGL_BAD_PARAMETER,
                             "target 2D texture does not have a valid size at specified level.");
            }

            if (level > 0 && (!texture->isMipmapComplete() ||
                              static_cast<size_t>(level) >= texture->getMipCompleteLevels()))
            {
                return Error(EGL_BAD_PARAMETER, "texture must be complete if level is non-zero.");
            }

            if (level == 0 && !texture->isMipmapComplete() &&
                TextureHasNonZeroMipLevelsSpecified(context, texture))
            {
                return Error(EGL_BAD_PARAMETER,
                             "if level is zero and the texture is incomplete, it must have no mip "
                             "levels specified except zero.");
            }
        }
        break;

        case EGL_GL_TEXTURE_CUBE_MAP_POSITIVE_X_KHR:
        case EGL_GL_TEXTURE_CUBE_MAP_NEGATIVE_X_KHR:
        case EGL_GL_TEXTURE_CUBE_MAP_POSITIVE_Y_KHR:
        case EGL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Y_KHR:
        case EGL_GL_TEXTURE_CUBE_MAP_POSITIVE_Z_KHR:
        case EGL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Z_KHR:
        {
            if (!displayExtensions.glTextureCubemapImage)
            {
                return Error(EGL_BAD_PARAMETER, "KHR_gl_texture_cubemap_image not supported.");
            }

            if (buffer == 0)
            {
                return Error(EGL_BAD_PARAMETER,
                             "buffer cannot reference a cubemap texture with the name 0.");
            }

            const gl::Texture *texture =
                context->getTexture(egl_gl::EGLClientBufferToGLObjectHandle(buffer));
            if (texture == nullptr || texture->getTarget() != GL_TEXTURE_CUBE_MAP)
            {
                return Error(EGL_BAD_PARAMETER, "target is not a cubemap texture.");
            }

            if (texture->getBoundSurface() != nullptr)
            {
                return Error(EGL_BAD_ACCESS, "texture has a surface bound to it.");
            }

            EGLAttrib level    = attributes.get(EGL_GL_TEXTURE_LEVEL_KHR, 0);
            GLenum cubeMapFace = egl_gl::EGLCubeMapTargetToGLCubeMapTarget(target);
            if (texture->getWidth(cubeMapFace, static_cast<size_t>(level)) == 0 ||
                texture->getHeight(cubeMapFace, static_cast<size_t>(level)) == 0)
            {
                return Error(EGL_BAD_PARAMETER,
                             "target cubemap texture does not have a valid size at specified level "
                             "and face.");
            }

            if (level > 0 && (!texture->isMipmapComplete() ||
                              static_cast<size_t>(level) >= texture->getMipCompleteLevels()))
            {
                return Error(EGL_BAD_PARAMETER, "texture must be complete if level is non-zero.");
            }

            if (level == 0 && !texture->isMipmapComplete() &&
                TextureHasNonZeroMipLevelsSpecified(context, texture))
            {
                return Error(EGL_BAD_PARAMETER,
                             "if level is zero and the texture is incomplete, it must have no mip "
                             "levels specified except zero.");
            }

            if (level == 0 && !texture->isMipmapComplete() &&
                CubeTextureHasUnspecifiedLevel0Face(texture))
            {
                return Error(EGL_BAD_PARAMETER,
                             "if level is zero and the texture is incomplete, it must have all of "
                             "its faces specified at level zero.");
            }
        }
        break;

        case EGL_GL_TEXTURE_3D_KHR:
        {
            if (!displayExtensions.glTexture3DImage)
            {
                return Error(EGL_BAD_PARAMETER, "KHR_gl_texture_3D_image not supported.");
            }

            if (buffer == 0)
            {
                return Error(EGL_BAD_PARAMETER,
                             "buffer cannot reference a 3D texture with the name 0.");
            }

            const gl::Texture *texture =
                context->getTexture(egl_gl::EGLClientBufferToGLObjectHandle(buffer));
            if (texture == nullptr || texture->getTarget() != GL_TEXTURE_3D)
            {
                return Error(EGL_BAD_PARAMETER, "target is not a 3D texture.");
            }

            if (texture->getBoundSurface() != nullptr)
            {
                return Error(EGL_BAD_ACCESS, "texture has a surface bound to it.");
            }

            EGLAttrib level   = attributes.get(EGL_GL_TEXTURE_LEVEL_KHR, 0);
            EGLAttrib zOffset = attributes.get(EGL_GL_TEXTURE_ZOFFSET_KHR, 0);
            if (texture->getWidth(GL_TEXTURE_3D, static_cast<size_t>(level)) == 0 ||
                texture->getHeight(GL_TEXTURE_3D, static_cast<size_t>(level)) == 0 ||
                texture->getDepth(GL_TEXTURE_3D, static_cast<size_t>(level)) == 0)
            {
                return Error(EGL_BAD_PARAMETER,
                             "target 3D texture does not have a valid size at specified level.");
            }

            if (static_cast<size_t>(zOffset) >=
                texture->getDepth(GL_TEXTURE_3D, static_cast<size_t>(level)))
            {
                return Error(EGL_BAD_PARAMETER,
                             "target 3D texture does not have enough layers for the specified Z "
                             "offset at the specified level.");
            }

            if (level > 0 && (!texture->isMipmapComplete() ||
                              static_cast<size_t>(level) >= texture->getMipCompleteLevels()))
            {
                return Error(EGL_BAD_PARAMETER, "texture must be complete if level is non-zero.");
            }

            if (level == 0 && !texture->isMipmapComplete() &&
                TextureHasNonZeroMipLevelsSpecified(context, texture))
            {
                return Error(EGL_BAD_PARAMETER,
                             "if level is zero and the texture is incomplete, it must have no mip "
                             "levels specified except zero.");
            }
        }
        break;

        case EGL_GL_RENDERBUFFER_KHR:
        {
            if (!displayExtensions.glRenderbufferImage)
            {
                return Error(EGL_BAD_PARAMETER, "KHR_gl_renderbuffer_image not supported.");
            }

            if (attributes.contains(EGL_GL_TEXTURE_LEVEL_KHR))
            {
                return Error(EGL_BAD_PARAMETER,
                             "EGL_GL_TEXTURE_LEVEL_KHR cannot be used in conjunction with a "
                             "renderbuffer target.");
            }

            if (buffer == 0)
            {
                return Error(EGL_BAD_PARAMETER,
                             "buffer cannot reference a renderbuffer with the name 0.");
            }

            const gl::Renderbuffer *renderbuffer =
                context->getRenderbuffer(egl_gl::EGLClientBufferToGLObjectHandle(buffer));
            if (renderbuffer == nullptr)
            {
                return Error(EGL_BAD_PARAMETER, "target is not a renderbuffer.");
            }

            if (renderbuffer->getSamples() > 0)
            {
                return Error(EGL_BAD_PARAMETER, "target renderbuffer cannot be multisampled.");
            }
        }
        break;

        default:
            return Error(EGL_BAD_PARAMETER, "invalid target: 0x%X", target);
    }

    return Error(EGL_SUCCESS);
}

Error ValidateDestroyImageKHR(const Display *display, const Image *image)
{
    Error error = ValidateImage(display, image);
    if (error.isError())
    {
        return error;
    }

    if (!display->getExtensions().imageBase && !display->getExtensions().image)
    {
        // It is out of spec what happens when calling an extension function when the extension is
        // not available.
        // EGL_BAD_DISPLAY seems like a reasonable error.
        return Error(EGL_BAD_DISPLAY);
    }

    return Error(EGL_SUCCESS);
}

Error ValidateCreateDeviceANGLE(EGLint device_type,
                                void *native_device,
                                const EGLAttrib *attrib_list)
{
    const ClientExtensions &clientExtensions = Display::getClientExtensions();
    if (!clientExtensions.deviceCreation)
    {
        return Error(EGL_BAD_ACCESS, "Device creation extension not active");
    }

    if (attrib_list != nullptr && attrib_list[0] != EGL_NONE)
    {
        return Error(EGL_BAD_ATTRIBUTE, "Invalid attrib_list parameter");
    }

    switch (device_type)
    {
        case EGL_D3D11_DEVICE_ANGLE:
            if (!clientExtensions.deviceCreationD3D11)
            {
                return Error(EGL_BAD_ATTRIBUTE, "D3D11 device creation extension not active");
            }
            break;
        default:
            return Error(EGL_BAD_ATTRIBUTE, "Invalid device_type parameter");
    }

    return Error(EGL_SUCCESS);
}

Error ValidateReleaseDeviceANGLE(Device *device)
{
    const ClientExtensions &clientExtensions = Display::getClientExtensions();
    if (!clientExtensions.deviceCreation)
    {
        return Error(EGL_BAD_ACCESS, "Device creation extension not active");
    }

    if (device == EGL_NO_DEVICE_EXT || !Device::IsValidDevice(device))
    {
        return Error(EGL_BAD_DEVICE_EXT, "Invalid device parameter");
    }

    Display *owningDisplay = device->getOwningDisplay();
    if (owningDisplay != nullptr)
    {
        return Error(EGL_BAD_DEVICE_EXT, "Device must have been created using eglCreateDevice");
    }

    return Error(EGL_SUCCESS);
}

Error ValidateCreateStreamKHR(const Display *display, const AttributeMap &attributes)
{
    Error error = ValidateDisplay(display);
    if (error.isError())
    {
        return error;
    }

    const DisplayExtensions &displayExtensions = display->getExtensions();
    if (!displayExtensions.stream)
    {
        return Error(EGL_BAD_ALLOC, "Stream extension not active");
    }

    for (const auto &attributeIter : attributes)
    {
        EGLAttrib attribute = attributeIter.first;
        EGLAttrib value     = attributeIter.second;

        error = ValidateStreamAttribute(attribute, value, displayExtensions);
        if (error.isError())
        {
            return error;
        }
    }

    return Error(EGL_SUCCESS);
}

Error ValidateDestroyStreamKHR(const Display *display, const Stream *stream)
{
    Error error = ValidateStream(display, stream);
    if (error.isError())
    {
        return error;
    }

    return Error(EGL_SUCCESS);
}

Error ValidateStreamAttribKHR(const Display *display,
                              const Stream *stream,
                              EGLint attribute,
                              EGLint value)
{
    Error error = ValidateStream(display, stream);
    if (error.isError())
    {
        return error;
    }

    if (stream->getState() == EGL_STREAM_STATE_DISCONNECTED_KHR)
    {
        return Error(EGL_BAD_STATE_KHR, "Bad stream state");
    }

    return ValidateStreamAttribute(attribute, value, display->getExtensions());
}

Error ValidateQueryStreamKHR(const Display *display,
                             const Stream *stream,
                             EGLenum attribute,
                             EGLint *value)
{
    Error error = ValidateStream(display, stream);
    if (error.isError())
    {
        return error;
    }

    switch (attribute)
    {
        case EGL_STREAM_STATE_KHR:
        case EGL_CONSUMER_LATENCY_USEC_KHR:
            break;
        case EGL_CONSUMER_ACQUIRE_TIMEOUT_USEC_KHR:
            if (!display->getExtensions().streamConsumerGLTexture)
            {
                return Error(EGL_BAD_ATTRIBUTE, "Consumer GLTexture extension not active");
            }
            break;
        default:
            return Error(EGL_BAD_ATTRIBUTE, "Invalid attribute");
    }

    return Error(EGL_SUCCESS);
}

Error ValidateQueryStreamu64KHR(const Display *display,
                                const Stream *stream,
                                EGLenum attribute,
                                EGLuint64KHR *value)
{
    Error error = ValidateStream(display, stream);
    if (error.isError())
    {
        return error;
    }

    switch (attribute)
    {
        case EGL_CONSUMER_FRAME_KHR:
        case EGL_PRODUCER_FRAME_KHR:
            break;
        default:
            return Error(EGL_BAD_ATTRIBUTE, "Invalid attribute");
    }

    return Error(EGL_SUCCESS);
}

Error ValidateStreamConsumerGLTextureExternalKHR(const Display *display,
                                                 gl::Context *context,
                                                 const Stream *stream)
{
    Error error = ValidateDisplay(display);
    if (error.isError())
    {
        return error;
    }

    error = ValidateContext(display, context);
    if (error.isError())
    {
        return error;
    }

    const DisplayExtensions &displayExtensions = display->getExtensions();
    if (!displayExtensions.streamConsumerGLTexture)
    {
        return Error(EGL_BAD_ACCESS, "Stream consumer extension not active");
    }

    if (stream == EGL_NO_STREAM_KHR || !display->isValidStream(stream))
    {
        return Error(EGL_BAD_STREAM_KHR, "Invalid stream");
    }

    if (stream->getState() != EGL_STREAM_STATE_CREATED_KHR)
    {
        return Error(EGL_BAD_STATE_KHR, "Invalid stream state");
    }

    return Error(EGL_SUCCESS);
}

Error ValidateStreamConsumerAcquireKHR(const Display *display,
                                       gl::Context *context,
                                       const Stream *stream)
{
    Error error = ValidateDisplay(display);
    if (error.isError())
    {
        return error;
    }

    error = ValidateContext(display, context);
    if (error.isError())
    {
        return error;
    }

    const DisplayExtensions &displayExtensions = display->getExtensions();
    if (!displayExtensions.streamConsumerGLTexture)
    {
        return Error(EGL_BAD_ACCESS, "Stream consumer extension not active");
    }

    if (stream == EGL_NO_STREAM_KHR || !display->isValidStream(stream))
    {
        return Error(EGL_BAD_STREAM_KHR, "Invalid stream");
    }

    if (stream->getState() != EGL_STREAM_STATE_NEW_FRAME_AVAILABLE_KHR &&
        stream->getState() != EGL_STREAM_STATE_OLD_FRAME_AVAILABLE_KHR)
    {
        return Error(EGL_BAD_STATE_KHR, "Invalid stream state");
    }

    return Error(EGL_SUCCESS);
}

Error ValidateStreamConsumerReleaseKHR(const Display *display,
                                       gl::Context *context,
                                       const Stream *stream)
{
    Error error = ValidateDisplay(display);
    if (error.isError())
    {
        return error;
    }

    error = ValidateContext(display, context);
    if (error.isError())
    {
        return error;
    }

    const DisplayExtensions &displayExtensions = display->getExtensions();
    if (!displayExtensions.streamConsumerGLTexture)
    {
        return Error(EGL_BAD_ACCESS, "Stream consumer extension not active");
    }

    if (stream == EGL_NO_STREAM_KHR || !display->isValidStream(stream))
    {
        return Error(EGL_BAD_STREAM_KHR, "Invalid stream");
    }

    if (stream->getState() != EGL_STREAM_STATE_NEW_FRAME_AVAILABLE_KHR &&
        stream->getState() != EGL_STREAM_STATE_OLD_FRAME_AVAILABLE_KHR)
    {
        return Error(EGL_BAD_STATE_KHR, "Invalid stream state");
    }

    return Error(EGL_SUCCESS);
}

Error ValidateStreamConsumerGLTextureExternalAttribsNV(const Display *display,
                                                       gl::Context *context,
                                                       const Stream *stream,
                                                       const AttributeMap &attribs)
{
    Error error = ValidateDisplay(display);
    if (error.isError())
    {
        return error;
    }

    error = ValidateContext(display, context);
    if (error.isError())
    {
        return Error(EGL_BAD_ACCESS, "Invalid context");
    }

    const DisplayExtensions &displayExtensions = display->getExtensions();
    if (!displayExtensions.streamConsumerGLTexture)
    {
        return Error(EGL_BAD_ACCESS, "Stream consumer extension not active");
    }

    if (stream == EGL_NO_STREAM_KHR || !display->isValidStream(stream))
    {
        return Error(EGL_BAD_STREAM_KHR, "Invalid stream");
    }

    if (stream->getState() != EGL_STREAM_STATE_CREATED_KHR)
    {
        return Error(EGL_BAD_STATE_KHR, "Invalid stream state");
    }

    EGLAttrib colorBufferType = EGL_RGB_BUFFER;
    EGLAttrib planeCount      = -1;
    EGLAttrib plane[3];
    for (int i = 0; i < 3; i++)
    {
        plane[i] = -1;
    }
    for (const auto &attributeIter : attribs)
    {
        EGLAttrib attribute = attributeIter.first;
        EGLAttrib value     = attributeIter.second;

        switch (attribute)
        {
            case EGL_COLOR_BUFFER_TYPE:
                if (value != EGL_RGB_BUFFER || value != EGL_YUV_BUFFER_EXT)
                {
                    return Error(EGL_BAD_PARAMETER, "Invalid color buffer type");
                }
                colorBufferType = value;
                break;
            case EGL_YUV_NUMBER_OF_PLANES_EXT:
                // planeCount = -1 is a tag for the default plane count so the value must be checked
                // to be positive here to ensure future logic doesn't break on invalid negative
                // inputs
                if (value < 0)
                {
                    return Error(EGL_BAD_MATCH, "Invalid plane count");
                }
                planeCount = value;
                break;
            default:
                if (attribute >= EGL_YUV_PLANE0_TEXTURE_UNIT_NV &&
                    attribute <= EGL_YUV_PLANE2_TEXTURE_UNIT_NV)
                {
                    if (value < 0)
                    {
                        return Error(EGL_BAD_ACCESS, "Invalid texture unit");
                    }
                    plane[attribute - EGL_YUV_PLANE0_TEXTURE_UNIT_NV] = value;
                }
                else
                {
                    return Error(EGL_BAD_ATTRIBUTE, "Invalid attribute");
                }
        }
    }

    if (colorBufferType == EGL_RGB_BUFFER)
    {
        if (planeCount > 0)
        {
            return Error(EGL_BAD_MATCH, "Plane count must be 0 for RGB buffer");
        }
        for (int i = 0; i < 3; i++)
        {
            if (plane[i] != -1)
            {
                return Error(EGL_BAD_MATCH, "Planes cannot be specified");
            }
        }
    }
    else
    {
        if (planeCount == -1)
        {
            planeCount = 2;
        }
        if (planeCount < 1 || planeCount > 3)
        {
            return Error(EGL_BAD_MATCH, "Invalid YUV plane count");
        }
        for (EGLAttrib i = 0; i < planeCount; i++)
        {
            if (plane[i] == -1)
            {
                return Error(EGL_BAD_MATCH, "Not all planes specified");
            }
        }
        for (EGLAttrib i = planeCount; i < 3; i++)
        {
            if (plane[i] != -1)
            {
                return Error(EGL_BAD_MATCH, "Invalid plane specified");
            }
        }
    }

    return Error(EGL_SUCCESS);
}
}
