Edit

kc3-lang/angle/src/libANGLE/validationEGL.cpp

Branch :

  • Show log

    Commit

  • Author : Jamie Madill
    Date : 2016-06-09 07:07:18
    Hash : dfde6abf
    Message : Context: Remove mutable gl::State getter. This will preserve layering - the API layer doesn't mutate the state directly, it passes the API call through to the Context. Is also removes the possiblity of any shenanigans of the Validation layer changing the GL state. Also, this CL refactors a few validation entry points to take ValidationContext instead of Context. ValidationContext will be the correct way to interact with the gl::Context in the Validation code. Finally, additional refactorings make ContextState a proper class with private data. This allows the ContextState itself to keep a mutable pointer to the gl::State, so ValidationContext can modify it if necessary (and it will be necessary for Framebuffer completeness caching). BUG=angleproject:1388 Change-Id: I86ab3561573caa9535c8d1b8aad4ab3d0e7cd470 Reviewed-on: https://chromium-review.googlesource.com/348954 Reviewed-by: Geoff Lang <geofflang@chromium.org> Reviewed-by: Corentin Wallez <cwallez@chromium.org> Commit-Queue: Jamie Madill <jmadill@chromium.org>

  • src/libANGLE/validationEGL.cpp
  • //
    // 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 "libANGLE/Texture.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);
    }
    
    egl::Error ValidateCreateImageKHRMipLevelCommon(gl::Context *context,
                                                    const gl::Texture *texture,
                                                    EGLAttrib level)
    {
        // Note that the spec EGL_KHR_create_image spec does not explicitly specify an error
        // when the level is outside the base/max level range, but it does mention that the
        // level "must be a part of the complete texture object <buffer>". It can be argued
        // that out-of-range levels are not a part of the complete texture.
        const GLuint effectiveBaseLevel = texture->getTextureState().getEffectiveBaseLevel();
        if (level > 0 &&
            (!texture->isMipmapComplete() || static_cast<GLuint>(level) < effectiveBaseLevel ||
             static_cast<GLuint>(level) > texture->getTextureState().getMipmapMaxLevel()))
        {
            return egl::Error(EGL_BAD_PARAMETER, "texture must be complete if level is non-zero.");
        }
    
        if (level == 0 && !texture->isMipmapComplete() &&
            TextureHasNonZeroMipLevelsSpecified(context, texture))
        {
            return egl::Error(EGL_BAD_PARAMETER,
                              "if level is zero and the texture is incomplete, it must have no mip "
                              "levels specified except zero.");
        }
    
        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)
    {
        ANGLE_TRY(ValidateDisplay(display));
    
        if (!display->isValidSurface(surface))
        {
            return Error(EGL_BAD_SURFACE);
        }
    
        return Error(EGL_SUCCESS);
    }
    
    Error ValidateConfig(const Display *display, const Config *config)
    {
        ANGLE_TRY(ValidateDisplay(display));
    
        if (!display->isValidConfig(config))
        {
            return Error(EGL_BAD_CONFIG);
        }
    
        return Error(EGL_SUCCESS);
    }
    
    Error ValidateContext(const Display *display, gl::Context *context)
    {
        ANGLE_TRY(ValidateDisplay(display));
    
        if (!display->isValidContext(context))
        {
            return Error(EGL_BAD_CONTEXT);
        }
    
        return Error(EGL_SUCCESS);
    }
    
    Error ValidateImage(const Display *display, const Image *image)
    {
        ANGLE_TRY(ValidateDisplay(display));
    
        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)
    {
        ANGLE_TRY(ValidateDisplay(display));
    
        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)
    {
        ANGLE_TRY(ValidateConfig(display, configuration));
    
        // 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)
    {
        ANGLE_TRY(ValidateConfig(display, config));
    
        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)
    {
        ANGLE_TRY(ValidateConfig(display, config));
    
        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)
    {
        ANGLE_TRY(ValidateConfig(display, config));
    
        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)
    {
        ANGLE_TRY(ValidateContext(display, context));
    
        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.");
                }
    
                ANGLE_TRY(ValidateCreateImageKHRMipLevelCommon(context, texture, level));
            }
            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.");
                }
    
                ANGLE_TRY(ValidateCreateImageKHRMipLevelCommon(context, texture, level));
    
                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.");
                }
    
                ANGLE_TRY(ValidateCreateImageKHRMipLevelCommon(context, texture, level));
            }
            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)
    {
        ANGLE_TRY(ValidateImage(display, image));
    
        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)
    {
        ANGLE_TRY(ValidateDisplay(display));
    
        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;
    
            ANGLE_TRY(ValidateStreamAttribute(attribute, value, displayExtensions));
        }
    
        return Error(EGL_SUCCESS);
    }
    
    Error ValidateDestroyStreamKHR(const Display *display, const Stream *stream)
    {
        ANGLE_TRY(ValidateStream(display, stream));
        return Error(EGL_SUCCESS);
    }
    
    Error ValidateStreamAttribKHR(const Display *display,
                                  const Stream *stream,
                                  EGLint attribute,
                                  EGLint value)
    {
        ANGLE_TRY(ValidateStream(display, stream));
    
        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)
    {
        ANGLE_TRY(ValidateStream(display, stream));
    
        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)
    {
        ANGLE_TRY(ValidateStream(display, stream));
    
        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)
    {
        ANGLE_TRY(ValidateDisplay(display));
        ANGLE_TRY(ValidateContext(display, context));
    
        const DisplayExtensions &displayExtensions = display->getExtensions();
        if (!displayExtensions.streamConsumerGLTexture)
        {
            return Error(EGL_BAD_ACCESS, "Stream consumer extension not active");
        }
    
        if (!context->getExtensions().eglStreamConsumerExternal)
        {
            return Error(EGL_BAD_ACCESS, "EGL stream consumer external GL extension not enabled");
        }
    
        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");
        }
    
        // Lookup the texture and ensure it is correct
        gl::Texture *texture = context->getGLState().getTargetTexture(GL_TEXTURE_EXTERNAL_OES);
        if (texture == nullptr || texture->getId() == 0)
        {
            return Error(EGL_BAD_ACCESS, "No external texture bound");
        }
    
        return Error(EGL_SUCCESS);
    }
    
    Error ValidateStreamConsumerAcquireKHR(const Display *display,
                                           gl::Context *context,
                                           const Stream *stream)
    {
        ANGLE_TRY(ValidateDisplay(display));
    
        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 (!context)
        {
            return Error(EGL_BAD_ACCESS, "No GL context current to calling thread.");
        }
    
        ANGLE_TRY(ValidateContext(display, context));
    
        if (!stream->isConsumerBoundToContext(context))
        {
            return Error(EGL_BAD_ACCESS, "Current GL context not associated with stream consumer");
        }
    
        if (stream->getConsumerType() != Stream::ConsumerType::GLTextureRGB &&
            stream->getConsumerType() != Stream::ConsumerType::GLTextureYUV)
        {
            return Error(EGL_BAD_ACCESS, "Invalid stream consumer type");
        }
    
        // Note: technically EGL_STREAM_STATE_EMPTY_KHR is a valid state when the timeout is non-zero.
        // However, the timeout is effectively ignored since it has no useful functionality with the
        // current producers that are implemented, so we don't allow that state
        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)
    {
        ANGLE_TRY(ValidateDisplay(display));
    
        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 (!context)
        {
            return Error(EGL_BAD_ACCESS, "No GL context current to calling thread.");
        }
    
        ANGLE_TRY(ValidateContext(display, context));
    
        if (!stream->isConsumerBoundToContext(context))
        {
            return Error(EGL_BAD_ACCESS, "Current GL context not associated with stream consumer");
        }
    
        if (stream->getConsumerType() != Stream::ConsumerType::GLTextureRGB &&
            stream->getConsumerType() != Stream::ConsumerType::GLTextureYUV)
        {
            return Error(EGL_BAD_ACCESS, "Invalid stream consumer type");
        }
    
        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)
    {
        ANGLE_TRY(ValidateDisplay(display));
    
        const DisplayExtensions &displayExtensions = display->getExtensions();
        if (!displayExtensions.streamConsumerGLTexture)
        {
            return Error(EGL_BAD_ACCESS, "Stream consumer extension not active");
        }
    
        // Although technically not a requirement in spec, the context needs to be checked for support
        // for external textures or future logic will cause assertations. This extension is also
        // effectively useless without external textures.
        if (!context->getExtensions().eglStreamConsumerExternal)
        {
            return Error(EGL_BAD_ACCESS, "EGL stream consumer external GL extension not enabled");
        }
    
        if (stream == EGL_NO_STREAM_KHR || !display->isValidStream(stream))
        {
            return Error(EGL_BAD_STREAM_KHR, "Invalid stream");
        }
    
        if (!context)
        {
            return Error(EGL_BAD_ACCESS, "No GL context current to calling thread.");
        }
    
        ANGLE_TRY(ValidateContext(display, context));
    
        if (stream->getState() != EGL_STREAM_STATE_CREATED_KHR)
        {
            return Error(EGL_BAD_STATE_KHR, "Invalid stream state");
        }
    
        const gl::Caps &glCaps = context->getCaps();
    
        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 ||
                             value >= static_cast<EGLAttrib>(glCaps.maxCombinedTextureImageUnits)) &&
                            value != EGL_NONE)
                        {
                            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");
                }
            }
    
            // Lookup the texture and ensure it is correct
            gl::Texture *texture = context->getGLState().getTargetTexture(GL_TEXTURE_EXTERNAL_OES);
            if (texture == nullptr || texture->getId() == 0)
            {
                return Error(EGL_BAD_ACCESS, "No external texture bound");
            }
        }
        else
        {
            if (planeCount == -1)
            {
                planeCount = 2;
            }
            if (planeCount < 1 || planeCount > 3)
            {
                return Error(EGL_BAD_MATCH, "Invalid YUV plane count");
            }
            for (EGLAttrib i = planeCount; i < 3; i++)
            {
                if (plane[i] != -1)
                {
                    return Error(EGL_BAD_MATCH, "Invalid plane specified");
                }
            }
    
            // Set to ensure no texture is referenced more than once
            std::set<gl::Texture *> textureSet;
            for (EGLAttrib i = 0; i < planeCount; i++)
            {
                if (plane[i] == -1)
                {
                    return Error(EGL_BAD_MATCH, "Not all planes specified");
                }
                if (plane[i] != EGL_NONE)
                {
                    gl::Texture *texture = context->getGLState().getSamplerTexture(
                        static_cast<unsigned int>(plane[i]), GL_TEXTURE_EXTERNAL_OES);
                    if (texture == nullptr || texture->getId() == 0)
                    {
                        return Error(
                            EGL_BAD_ACCESS,
                            "No external texture bound at one or more specified texture units");
                    }
                    if (textureSet.find(texture) != textureSet.end())
                    {
                        return Error(EGL_BAD_ACCESS, "Multiple planes bound to same texture object");
                    }
                    textureSet.insert(texture);
                }
            }
        }
    
        return Error(EGL_SUCCESS);
    }
    
    Error ValidateCreateStreamProducerD3DTextureNV12ANGLE(const Display *display,
                                                          const Stream *stream,
                                                          const AttributeMap &attribs)
    {
        const DisplayExtensions &displayExtensions = display->getExtensions();
        if (!displayExtensions.streamProducerD3DTextureNV12)
        {
            return Error(EGL_BAD_ACCESS, "Stream producer extension not active");
        }
    
        ANGLE_TRY(ValidateStream(display, stream));
    
        if (!attribs.isEmpty())
        {
            return Error(EGL_BAD_ATTRIBUTE, "Invalid attribute");
        }
    
        if (stream->getState() != EGL_STREAM_STATE_CONNECTING_KHR)
        {
            return Error(EGL_BAD_STATE_KHR, "Stream not in connecting state");
        }
    
        if (stream->getConsumerType() != Stream::ConsumerType::GLTextureYUV ||
            stream->getPlaneCount() != 2)
        {
            return Error(EGL_BAD_MATCH, "Incompatible stream consumer type");
        }
    
        return Error(EGL_SUCCESS);
    }
    
    Error ValidateStreamPostD3DTextureNV12ANGLE(const Display *display,
                                                const Stream *stream,
                                                void *texture,
                                                const AttributeMap &attribs)
    {
        ANGLE_TRY(ValidateDisplay(display));
    
        const DisplayExtensions &displayExtensions = display->getExtensions();
        if (!displayExtensions.streamProducerD3DTextureNV12)
        {
            return Error(EGL_BAD_ACCESS, "Stream producer extension not active");
        }
    
        ANGLE_TRY(ValidateStream(display, stream));
    
        for (auto &attributeIter : attribs)
        {
            EGLAttrib attribute = attributeIter.first;
            EGLAttrib value     = attributeIter.second;
    
            switch (attribute)
            {
                case EGL_D3D_TEXTURE_SUBRESOURCE_ID_ANGLE:
                    if (value < 0)
                    {
                        return Error(EGL_BAD_PARAMETER, "Invalid subresource index");
                    }
                    break;
                default:
                    return Error(EGL_BAD_ATTRIBUTE, "Invalid attribute");
            }
        }
    
        if (stream->getState() != EGL_STREAM_STATE_EMPTY_KHR &&
            stream->getState() != EGL_STREAM_STATE_NEW_FRAME_AVAILABLE_KHR &&
            stream->getState() != EGL_STREAM_STATE_OLD_FRAME_AVAILABLE_KHR)
        {
            return Error(EGL_BAD_STATE_KHR, "Stream not fully configured");
        }
    
        if (stream->getProducerType() != Stream::ProducerType::D3D11TextureNV12)
        {
            return Error(EGL_BAD_MATCH, "Incompatible stream producer");
        }
    
        if (texture == nullptr)
        {
            return egl::Error(EGL_BAD_PARAMETER, "Texture is null");
        }
    
        return stream->validateD3D11NV12Texture(texture);
    }
    }  // namespace gl