EGLBoolean EGLAPIENTRY SwapInterval(EGLDisplay dpy, EGLint interval)
{
    EVENT("(EGLDisplay dpy = 0x%0.8p, EGLint interval = %d)", dpy, interval);

    Display *display = static_cast<Display*>(dpy);

    Error error = ValidateDisplay(display);
    if (error.isError())
    {
        SetGlobalError(error);
        return EGL_FALSE;
    }

    Surface *draw_surface = static_cast<Surface*>(GetGlobalDrawSurface());

    if (draw_surface == NULL)
    {
        SetGlobalError(Error(EGL_BAD_SURFACE));
        return EGL_FALSE;
    }

    const egl::Config *surfaceConfig = draw_surface->getConfig();
    EGLint clampedInterval = std::min(std::max(interval, surfaceConfig->minSwapInterval), surfaceConfig->maxSwapInterval);

    draw_surface->setSwapInterval(clampedInterval);

    SetGlobalError(Error(EGL_SUCCESS));
    return EGL_TRUE;
}
Beispiel #2
0
EGLBoolean EGLAPIENTRY SwapInterval(EGLDisplay dpy, EGLint interval)
{
    EVENT("(EGLDisplay dpy = 0x%0.8p, EGLint interval = %d)", dpy, interval);

    Display *display = static_cast<Display*>(dpy);

    Error error = ValidateDisplay(display);
    if (error.isError())
    {
        SetGlobalError(error);
        return EGL_FALSE;
    }

    Surface *draw_surface = static_cast<Surface*>(GetGlobalDrawSurface());

    if (draw_surface == NULL)
    {
        SetGlobalError(Error(EGL_BAD_SURFACE));
        return EGL_FALSE;
    }

    draw_surface->setSwapInterval(interval);

    SetGlobalError(Error(EGL_SUCCESS));
    return EGL_TRUE;
}
// EGL_EXT_device_query
EGLBoolean EGLAPIENTRY QueryDisplayAttribEXT(EGLDisplay dpy, EGLint attribute, EGLAttrib *value)
{
    EVENT("(EGLDisplay dpy = 0x%0.8p, EGLint attribute = %d, EGLAttrib *value = 0x%0.8p)",
          dpy, attribute, value);

    Display *display = static_cast<Display*>(dpy);

    Error error = ValidateDisplay(display);
    if (error.isError())
    {
        SetGlobalError(error);
        return EGL_FALSE;
    }

    if (!display->getExtensions().deviceQuery)
    {
        SetGlobalError(Error(EGL_BAD_ACCESS));
        return EGL_FALSE;
    }

    // validate the attribute parameter
    switch (attribute)
    {
      case EGL_DEVICE_EXT:
        *value = reinterpret_cast<EGLAttrib>(display->getDevice());
        break;

      default:
        SetGlobalError(Error(EGL_BAD_ATTRIBUTE));
        return EGL_FALSE;
    }

    SetGlobalError(error);
    return (error.isError() ? EGL_FALSE : EGL_TRUE);
}
EGLBoolean EGLAPIENTRY SwapInterval(EGLDisplay dpy, EGLint interval)
{
    EVENT("(EGLDisplay dpy = 0x%0.8p, EGLint interval = %d)", dpy, interval);
    Thread *thread = GetCurrentThread();

    Display *display = static_cast<Display *>(dpy);

    Error error = ValidateDisplay(display);
    if (error.isError())
    {
        thread->setError(error);
        return EGL_FALSE;
    }

    Surface *draw_surface = static_cast<Surface *>(thread->getCurrentDrawSurface());

    if (draw_surface == nullptr)
    {
        thread->setError(EglBadSurface());
        return EGL_FALSE;
    }

    const egl::Config *surfaceConfig = draw_surface->getConfig();
    EGLint clampedInterval           = std::min(std::max(interval, surfaceConfig->minSwapInterval),
                                      surfaceConfig->maxSwapInterval);

    draw_surface->setSwapInterval(clampedInterval);

    thread->setError(NoError());
    return EGL_TRUE;
}
EGLBoolean EGLAPIENTRY WaitGL(void)
{
    EVENT("()");
    Thread *thread = GetCurrentThread();

    Display *display = thread->getCurrentDisplay();

    Error error = ValidateDisplay(display);
    if (error.isError())
    {
        thread->setError(error);
        return EGL_FALSE;
    }

    // eglWaitGL like calling eglWaitClient with the OpenGL ES API bound. Since we only implement
    // OpenGL ES we can do the call directly.
    error = display->waitClient(thread->getContext());
    if (error.isError())
    {
        thread->setError(error);
        return EGL_FALSE;
    }

    thread->setError(NoError());
    return EGL_TRUE;
}
EGLBoolean EGLAPIENTRY WaitNative(EGLint engine)
{
    EVENT("(EGLint engine = %d)", engine);
    Thread *thread = GetCurrentThread();

    Display *display = thread->getCurrentDisplay();

    Error error = ValidateDisplay(display);
    if (error.isError())
    {
        thread->setError(error);
        return EGL_FALSE;
    }

    if (engine != EGL_CORE_NATIVE_ENGINE)
    {
        thread->setError(EglBadParameter() << "the 'engine' parameter has an unrecognized value");
    }

    error = display->waitNative(thread->getContext(), engine);
    if (error.isError())
    {
        thread->setError(error);
        return EGL_FALSE;
    }

    thread->setError(NoError());
    return EGL_TRUE;
}
Beispiel #7
0
Error ValidateCreateStreamProducerD3DTextureNV12ANGLE(const Display *display,
        const Stream *stream,
        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));

    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);
}
EGLBoolean EGLAPIENTRY ChooseConfig(EGLDisplay dpy, const EGLint *attrib_list, EGLConfig *configs, EGLint config_size, EGLint *num_config)
{
    EVENT("(EGLDisplay dpy = 0x%0.8p, const EGLint *attrib_list = 0x%0.8p, "
          "EGLConfig *configs = 0x%0.8p, EGLint config_size = %d, EGLint *num_config = 0x%0.8p)",
          dpy, attrib_list, configs, config_size, num_config);

    Display *display = static_cast<Display*>(dpy);

    Error error = ValidateDisplay(display);
    if (error.isError())
    {
        SetGlobalError(error);
        return EGL_FALSE;
    }

    if (!num_config)
    {
        SetGlobalError(Error(EGL_BAD_PARAMETER));
        return EGL_FALSE;
    }

    std::vector<const Config*> filteredConfigs = display->getConfigs(AttributeMap(attrib_list));
    if (configs)
    {
        filteredConfigs.resize(std::min<size_t>(filteredConfigs.size(), config_size));
        for (size_t i = 0; i < filteredConfigs.size(); i++)
        {
            configs[i] = const_cast<Config*>(filteredConfigs[i]);
        }
    }
    *num_config = static_cast<EGLint>(filteredConfigs.size());

    SetGlobalError(Error(EGL_SUCCESS));
    return EGL_TRUE;
}
Beispiel #9
0
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);
}
Beispiel #10
0
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);
}
Beispiel #11
0
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);
}
Beispiel #12
0
Error ValidateSurface(const Display *display, Surface *surface)
{
    ANGLE_TRY(ValidateDisplay(display));

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

    return Error(EGL_SUCCESS);
}
Beispiel #13
0
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);
}
Beispiel #14
0
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);
}
Beispiel #15
0
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);
}
Beispiel #16
0
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);
}
Beispiel #17
0
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);
}
Beispiel #18
0
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);
}
Beispiel #19
0
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);
}
const char *EGLAPIENTRY QueryString(EGLDisplay dpy, EGLint name)
{
    EVENT("(EGLDisplay dpy = 0x%0.8p, EGLint name = %d)", dpy, name);
    Thread *thread = GetCurrentThread();

    Display *display = static_cast<Display *>(dpy);
    if (!(display == EGL_NO_DISPLAY && name == EGL_EXTENSIONS))
    {
        Error error = ValidateDisplay(display);
        if (error.isError())
        {
            thread->setError(error);
            return nullptr;
        }
    }

    const char *result;
    switch (name)
    {
        case EGL_CLIENT_APIS:
            result = "OpenGL_ES";
            break;
        case EGL_EXTENSIONS:
            if (display == EGL_NO_DISPLAY)
            {
                result = Display::GetClientExtensionString().c_str();
            }
            else
            {
                result = display->getExtensionString().c_str();
            }
            break;
        case EGL_VENDOR:
            result = display->getVendorString().c_str();
            break;
        case EGL_VERSION:
            result = "1.4 (ANGLE " ANGLE_VERSION_STRING ")";
            break;
        default:
            thread->setError(EglBadParameter());
            return nullptr;
    }

    thread->setError(NoError());
    return result;
}
const char *EGLAPIENTRY QueryString(EGLDisplay dpy, EGLint name)
{
    EVENT("(EGLDisplay dpy = 0x%0.8p, EGLint name = %d)", dpy, name);

    Display *display = static_cast<Display*>(dpy);
    if (!(display == EGL_NO_DISPLAY && name == EGL_EXTENSIONS))
    {
        Error error = ValidateDisplay(display);
        if (error.isError())
        {
            SetGlobalError(error);
            return NULL;
        }
    }

    const char *result;
    switch (name)
    {
      case EGL_CLIENT_APIS:
        result = "OpenGL_ES";
        break;
      case EGL_EXTENSIONS:
        if (display == EGL_NO_DISPLAY)
        {
            result = Display::getClientExtensionString().c_str();
        }
        else
        {
            result = display->getExtensionString().c_str();
        }
        break;
      case EGL_VENDOR:
        result = display->getVendorString().c_str();
        break;
      case EGL_VERSION:
        result = "1.4 (ANGLE " ANGLE_VERSION_STRING ")";
        break;
      default:
        SetGlobalError(Error(EGL_BAD_PARAMETER));
        return NULL;
    }

    SetGlobalError(Error(EGL_SUCCESS));
    return result;
}
Beispiel #22
0
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);
}
Beispiel #23
0
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);
}
Beispiel #24
0
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);
}
EGLBoolean EGLAPIENTRY WaitClient(void)
{
    EVENT("()");
    Thread *thread = GetCurrentThread();

    Display *display = thread->getCurrentDisplay();

    Error error = ValidateDisplay(display);
    if (error.isError())
    {
        thread->setError(error);
        return EGL_FALSE;
    }

    error = display->waitClient(thread->getContext());
    if (error.isError())
    {
        thread->setError(error);
        return EGL_FALSE;
    }

    thread->setError(NoError());
    return EGL_TRUE;
}
Beispiel #26
0
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);
}