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

#include <gtest/gtest.h>

#include <vector>

#include "media/yuvtest.inl"
#include "test_utils/ANGLETest.h"
#include "test_utils/gl_raii.h"
#include "util/EGLWindow.h"
#include "util/OSWindow.h"

using namespace angle;

namespace
{

bool CheckNV12TextureSupport(ID3D11Device *device)
{
    HRESULT result;
    UINT formatSupport;
    result = device->CheckFormatSupport(DXGI_FORMAT_NV12, &formatSupport);
    if (result == E_FAIL)
    {
        return false;
    }
    return (formatSupport & D3D11_FORMAT_SUPPORT_TEXTURE2D) != 0;
}

class EGLStreamTest : public ANGLETest
{
  protected:
    EGLStreamTest()
    {
        setWindowWidth(128);
        setWindowHeight(128);
        setConfigRedBits(8);
        setConfigGreenBits(8);
        setConfigBlueBits(8);
        setConfigAlphaBits(8);
        setConfigDepthBits(24);
    }
};

// Tests validation of the stream API
TEST_P(EGLStreamTest, StreamValidationTest)
{
    EGLWindow *window            = getEGLWindow();
    EGLDisplay display           = window->getDisplay();
    const char *extensionsString = eglQueryString(display, EGL_EXTENSIONS);
    if (strstr(extensionsString, "EGL_KHR_stream") == nullptr)
    {
        std::cout << "Stream extension not supported" << std::endl;
        return;
    }

    const EGLint streamAttributesBad[] = {
        EGL_STREAM_STATE_KHR,
        0,
        EGL_NONE,
        EGL_PRODUCER_FRAME_KHR,
        0,
        EGL_NONE,
        EGL_CONSUMER_FRAME_KHR,
        0,
        EGL_NONE,
        EGL_CONSUMER_LATENCY_USEC_KHR,
        -1,
        EGL_NONE,
        EGL_RED_SIZE,
        EGL_DONT_CARE,
        EGL_NONE,
    };

    // Validate create stream attributes
    EGLStreamKHR stream = eglCreateStreamKHR(display, &streamAttributesBad[0]);
    ASSERT_EGL_ERROR(EGL_BAD_ACCESS);
    ASSERT_EQ(EGL_NO_STREAM_KHR, stream);

    stream = eglCreateStreamKHR(display, &streamAttributesBad[3]);
    ASSERT_EGL_ERROR(EGL_BAD_ACCESS);
    ASSERT_EQ(EGL_NO_STREAM_KHR, stream);

    stream = eglCreateStreamKHR(display, &streamAttributesBad[6]);
    ASSERT_EGL_ERROR(EGL_BAD_ACCESS);
    ASSERT_EQ(EGL_NO_STREAM_KHR, stream);

    stream = eglCreateStreamKHR(display, &streamAttributesBad[9]);
    ASSERT_EGL_ERROR(EGL_BAD_PARAMETER);
    ASSERT_EQ(EGL_NO_STREAM_KHR, stream);

    stream = eglCreateStreamKHR(display, &streamAttributesBad[12]);
    ASSERT_EGL_ERROR(EGL_BAD_ATTRIBUTE);
    ASSERT_EQ(EGL_NO_STREAM_KHR, stream);

    const EGLint streamAttributes[] = {
        EGL_CONSUMER_LATENCY_USEC_KHR,
        0,
        EGL_NONE,
    };

    stream = eglCreateStreamKHR(EGL_NO_DISPLAY, streamAttributes);
    ASSERT_EGL_ERROR(EGL_BAD_DISPLAY);
    ASSERT_EQ(EGL_NO_STREAM_KHR, stream);

    // Create an actual stream
    stream = eglCreateStreamKHR(display, streamAttributes);
    ASSERT_EGL_SUCCESS();
    ASSERT_NE(EGL_NO_STREAM_KHR, stream);

    // Assert it is in the created state
    EGLint state;
    eglQueryStreamKHR(display, stream, EGL_STREAM_STATE_KHR, &state);
    ASSERT_EGL_SUCCESS();
    ASSERT_EQ(EGL_STREAM_STATE_CREATED_KHR, state);

    // Test getting and setting the latency
    EGLint latency = 10;
    eglStreamAttribKHR(display, stream, EGL_CONSUMER_LATENCY_USEC_KHR, latency);
    ASSERT_EGL_SUCCESS();
    eglQueryStreamKHR(display, stream, EGL_CONSUMER_LATENCY_USEC_KHR, &latency);
    ASSERT_EGL_SUCCESS();
    ASSERT_EQ(10, latency);
    eglStreamAttribKHR(display, stream, EGL_CONSUMER_LATENCY_USEC_KHR, -1);
    ASSERT_EGL_ERROR(EGL_BAD_PARAMETER);
    ASSERT_EQ(10, latency);

    // Test the 64-bit queries
    EGLuint64KHR value;
    eglQueryStreamu64KHR(display, stream, EGL_CONSUMER_FRAME_KHR, &value);
    ASSERT_EGL_SUCCESS();
    eglQueryStreamu64KHR(display, stream, EGL_PRODUCER_FRAME_KHR, &value);
    ASSERT_EGL_SUCCESS();

    // Destroy the stream
    eglDestroyStreamKHR(display, stream);
    ASSERT_EGL_SUCCESS();
}

// Tests validation of stream consumer gltexture API
TEST_P(EGLStreamTest, StreamConsumerGLTextureValidationTest)
{
    EGLWindow *window            = getEGLWindow();
    EGLDisplay display           = window->getDisplay();
    const char *extensionsString = eglQueryString(display, EGL_EXTENSIONS);
    if (strstr(extensionsString, "EGL_KHR_stream_consumer_gltexture") == nullptr)
    {
        std::cout << "Stream consumer gltexture extension not supported" << std::endl;
        return;
    }

    const EGLint streamAttributes[] = {
        EGL_CONSUMER_LATENCY_USEC_KHR, 0, EGL_CONSUMER_ACQUIRE_TIMEOUT_USEC_KHR, 0, EGL_NONE,
    };

    EGLStreamKHR stream = eglCreateStreamKHR(display, streamAttributes);
    ASSERT_EGL_SUCCESS();

    EGLBoolean result = eglStreamConsumerGLTextureExternalKHR(display, stream);
    ASSERT_EGL_FALSE(result);
    ASSERT_EGL_ERROR(EGL_BAD_ACCESS);

    GLuint tex;
    glGenTextures(1, &tex);
    glBindTexture(GL_TEXTURE_EXTERNAL_OES, tex);
    result = eglStreamConsumerGLTextureExternalKHR(display, stream);
    ASSERT_EGL_TRUE(result);
    ASSERT_EGL_SUCCESS();

    EGLint state;
    eglQueryStreamKHR(display, stream, EGL_STREAM_STATE_KHR, &state);
    ASSERT_EGL_SUCCESS();
    ASSERT_EQ(EGL_STREAM_STATE_CONNECTING_KHR, state);

    eglDestroyStreamKHR(display, stream);
    ASSERT_EGL_SUCCESS();
}

// Tests validation of stream consumer gltexture yuv API
TEST_P(EGLStreamTest, StreamConsumerGLTextureYUVValidationTest)
{
    EGLWindow *window            = getEGLWindow();
    EGLDisplay display           = window->getDisplay();
    const char *extensionsString = eglQueryString(display, EGL_EXTENSIONS);
    if (strstr(extensionsString, "EGL_NV_stream_consumer_gltexture_yuv") == nullptr)
    {
        std::cout << "Stream consumer gltexture yuv extension not supported" << std::endl;
        return;
    }

    const EGLint streamAttributes[] = {
        EGL_CONSUMER_LATENCY_USEC_KHR, 0, EGL_CONSUMER_ACQUIRE_TIMEOUT_USEC_KHR, 0, EGL_NONE,
    };

    EGLStreamKHR stream = eglCreateStreamKHR(display, streamAttributes);
    ASSERT_EGL_SUCCESS();

    EGLAttrib consumerAttributesBad[] = {
        EGL_COLOR_BUFFER_TYPE,
        EGL_YUV_BUFFER_EXT,  // 0
        EGL_YUV_NUMBER_OF_PLANES_EXT,
        0,
        EGL_NONE,
        EGL_COLOR_BUFFER_TYPE,
        EGL_YUV_BUFFER_EXT,  // 5
        EGL_YUV_NUMBER_OF_PLANES_EXT,
        1,
        EGL_NONE,
        EGL_COLOR_BUFFER_TYPE,
        EGL_YUV_BUFFER_EXT,  // 10
        EGL_YUV_NUMBER_OF_PLANES_EXT,
        1,
        EGL_YUV_PLANE0_TEXTURE_UNIT_NV,
        9999,
        EGL_NONE,
        EGL_COLOR_BUFFER_TYPE,
        EGL_YUV_BUFFER_EXT,  // 17
        EGL_YUV_NUMBER_OF_PLANES_EXT,
        1,
        EGL_YUV_PLANE0_TEXTURE_UNIT_NV,
        0,
        EGL_YUV_PLANE1_TEXTURE_UNIT_NV,
        1,
        EGL_NONE,
        EGL_COLOR_BUFFER_TYPE,
        EGL_YUV_BUFFER_EXT,  // 26
        EGL_YUV_NUMBER_OF_PLANES_EXT,
        2,
        EGL_YUV_PLANE0_TEXTURE_UNIT_NV,
        0,
        EGL_YUV_PLANE1_TEXTURE_UNIT_NV,
        0,
        EGL_NONE,
    };

    EGLAttrib consumerAttributes[] = {
        EGL_COLOR_BUFFER_TYPE,
        EGL_YUV_BUFFER_EXT,
        EGL_YUV_NUMBER_OF_PLANES_EXT,
        2,
        EGL_YUV_PLANE0_TEXTURE_UNIT_NV,
        0,
        EGL_YUV_PLANE1_TEXTURE_UNIT_NV,
        1,
        EGL_NONE,
    };

    EGLBoolean result =
        eglStreamConsumerGLTextureExternalAttribsNV(display, stream, &consumerAttributesBad[0]);
    ASSERT_EGL_FALSE(result);
    ASSERT_EGL_ERROR(EGL_BAD_MATCH);

    result =
        eglStreamConsumerGLTextureExternalAttribsNV(display, stream, &consumerAttributesBad[5]);
    ASSERT_EGL_FALSE(result);
    ASSERT_EGL_ERROR(EGL_BAD_MATCH);

    result =
        eglStreamConsumerGLTextureExternalAttribsNV(display, stream, &consumerAttributesBad[10]);
    ASSERT_EGL_FALSE(result);
    ASSERT_EGL_ERROR(EGL_BAD_ACCESS);

    result =
        eglStreamConsumerGLTextureExternalAttribsNV(display, stream, &consumerAttributesBad[17]);
    ASSERT_EGL_FALSE(result);
    ASSERT_EGL_ERROR(EGL_BAD_MATCH);

    result = eglStreamConsumerGLTextureExternalAttribsNV(display, stream, consumerAttributes);
    ASSERT_EGL_FALSE(result);
    ASSERT_EGL_ERROR(EGL_BAD_ACCESS);

    GLuint tex[2];
    glGenTextures(2, tex);
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_EXTERNAL_OES, tex[0]);
    glActiveTexture(GL_TEXTURE1);
    glBindTexture(GL_TEXTURE_EXTERNAL_OES, tex[1]);

    result =
        eglStreamConsumerGLTextureExternalAttribsNV(display, stream, &consumerAttributesBad[26]);
    ASSERT_EGL_FALSE(result);
    ASSERT_EGL_ERROR(EGL_BAD_ACCESS);

    result = eglStreamConsumerGLTextureExternalAttribsNV(display, stream, consumerAttributes);
    ASSERT_EGL_TRUE(result);
    ASSERT_EGL_SUCCESS();

    EGLint state;
    eglQueryStreamKHR(display, stream, EGL_STREAM_STATE_KHR, &state);
    ASSERT_EGL_SUCCESS();
    ASSERT_EQ(EGL_STREAM_STATE_CONNECTING_KHR, state);

    eglDestroyStreamKHR(display, stream);
    ASSERT_EGL_SUCCESS();
}

// Tests that deleting a texture invalidates the associated stream
TEST_P(EGLStreamTest, StreamConsumerGLTextureYUVDeletionTest)
{
    EGLWindow *window            = getEGLWindow();
    EGLDisplay display           = window->getDisplay();
    const char *extensionsString = eglQueryString(display, EGL_EXTENSIONS);
    if (strstr(extensionsString, "EGL_ANGLE_stream_producer_d3d_texture") == nullptr)
    {
        std::cout << "Stream producer d3d texture not supported" << std::endl;
        return;
    }

    const EGLint streamAttributes[] = {
        EGL_CONSUMER_LATENCY_USEC_KHR, 0, EGL_CONSUMER_ACQUIRE_TIMEOUT_USEC_KHR, 0, EGL_NONE,
    };

    EGLStreamKHR stream = eglCreateStreamKHR(display, streamAttributes);
    ASSERT_EGL_SUCCESS();

    EGLAttrib consumerAttributes[] = {
        EGL_COLOR_BUFFER_TYPE,
        EGL_YUV_BUFFER_EXT,
        EGL_YUV_NUMBER_OF_PLANES_EXT,
        2,
        EGL_YUV_PLANE0_TEXTURE_UNIT_NV,
        0,
        EGL_YUV_PLANE1_TEXTURE_UNIT_NV,
        1,
        EGL_NONE,
    };

    GLuint tex[2];
    glGenTextures(2, tex);
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_EXTERNAL_OES, tex[0]);
    glActiveTexture(GL_TEXTURE1);
    glBindTexture(GL_TEXTURE_EXTERNAL_OES, tex[1]);

    EGLBoolean result =
        eglStreamConsumerGLTextureExternalAttribsNV(display, stream, consumerAttributes);
    ASSERT_EGL_TRUE(result);
    ASSERT_EGL_SUCCESS();

    EGLAttrib producerAttributes[] = {
        EGL_NONE,
    };

    result = eglCreateStreamProducerD3DTextureANGLE(display, stream, producerAttributes);
    ASSERT_EGL_TRUE(result);
    ASSERT_EGL_SUCCESS();

    EGLint state;
    eglQueryStreamKHR(display, stream, EGL_STREAM_STATE_KHR, &state);
    ASSERT_EGL_SUCCESS();
    ASSERT_EQ(EGL_STREAM_STATE_EMPTY_KHR, state);

    // Delete the first texture, which should be enough to invalidate the stream
    glDeleteTextures(1, tex);

    eglQueryStreamKHR(display, stream, EGL_STREAM_STATE_KHR, &state);
    ASSERT_EGL_SUCCESS();
    ASSERT_EQ(EGL_STREAM_STATE_DISCONNECTED_KHR, state);

    eglDestroyStreamKHR(display, stream);
    ASSERT_EGL_SUCCESS();
}

class D3D11TextureStreamSamplingTest : public ANGLETest
{
  protected:
    void SetUp() override
    {
        ANGLETest::SetUp();

        EGLWindow *window = getEGLWindow();
        mDisplay          = window->getDisplay();
        if (!eglDisplayExtensionEnabled(mDisplay, "EGL_ANGLE_stream_producer_d3d_texture"))
        {
            std::cout << "Stream producer d3d texture not supported" << std::endl;
            return;
        }

        glGenRenderbuffers(1, &mRB);
        glBindRenderbuffer(GL_RENDERBUFFER, mRB);
        glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, 2, 2);
        glBindRenderbuffer(GL_RENDERBUFFER, 0);

        glGenFramebuffers(1, &mFB);
        glBindFramebuffer(GL_FRAMEBUFFER, mFB);
        glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, mRB);
        ASSERT_GLENUM_EQ(GL_FRAMEBUFFER_COMPLETE, glCheckFramebufferStatus(GL_FRAMEBUFFER));

        glViewport(0, 0, 2, 2);
        glClearColor(1, 0, 0, 1);
        glClear(GL_COLOR_BUFFER_BIT);
        ASSERT_GL_NO_ERROR();

        uint32_t pixel;
        glReadPixels(0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, &pixel);
        ASSERT_EQ(pixel, 0xff0000ff);

        mStream = eglCreateStreamKHR(mDisplay, nullptr);
        ASSERT_EGL_SUCCESS();

        EGLDeviceEXT eglDevice;
        eglQueryDisplayAttribEXT(mDisplay, EGL_DEVICE_EXT, (EGLAttrib *)&eglDevice);
        eglQueryDeviceAttribEXT(eglDevice, EGL_D3D11_DEVICE_ANGLE, (EGLAttrib *)&mD3D);
    }

    void TearDown() override
    {
        EGLBoolean result = eglDestroyStreamKHR(mDisplay, mStream);
        ASSERT_EGL_TRUE(result);
        ASSERT_EGL_SUCCESS();

        glDeleteRenderbuffers(1, &mRB);
        glDeleteFramebuffers(1, &mFB);

        ANGLETest::TearDown();
    }

    EGLDisplay mDisplay  = 0;
    EGLStreamKHR mStream = 0;
    GLuint mRB           = 0;
    GLuint mFB           = 0;
    ID3D11Device *mD3D;
};

// Test RGBA texture sampling via EGLStreams
TEST_P(D3D11TextureStreamSamplingTest, RGBA)
{
    EGLWindow *window  = getEGLWindow();
    EGLDisplay display = window->getDisplay();
    ANGLE_SKIP_TEST_IF(
        !eglDisplayExtensionEnabled(display, "EGL_ANGLE_stream_producer_d3d_texture"));

    constexpr char kVertShader[] = R"(
        attribute vec4 aPos;
        varying vec2 vTexCoord;
        void main()
        {
            gl_Position = aPos;
            vTexCoord = gl_Position.xy * 0.5 + 0.5;
        }
    )";
    constexpr char kFragShader[] = R"(
        #extension GL_NV_EGL_stream_consumer_external : require
        precision mediump float;
        uniform samplerExternalOES uTex;
        varying vec2 vTexCoord;
        void main()
        {
            gl_FragColor = texture2D(uTex, vTexCoord);
        }
    )";
    ANGLE_GL_PROGRAM(prog, kVertShader, kFragShader);
    glUseProgram(prog);
    glUniform1i(glGetUniformLocation(prog, "uTex"), 0);

    GLTexture tex;
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_EXTERNAL_OES, tex);
    glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    ASSERT_GL_NO_ERROR();

    EGLBoolean result = eglStreamConsumerGLTextureExternalAttribsNV(mDisplay, mStream, nullptr);
    ASSERT_EGL_TRUE(result);
    ASSERT_EGL_SUCCESS();
    glActiveTexture(GL_TEXTURE3);  // Set to an unused slot to ensure StreamConsumer picked up
                                   // the current slot.

    result = eglCreateStreamProducerD3DTextureANGLE(mDisplay, mStream, nullptr);
    ASSERT_EGL_TRUE(result);
    ASSERT_EGL_SUCCESS();

    // Create and post the d3d11 texture

    D3D11_TEXTURE2D_DESC desc = {};
    desc.Width                = 1;
    desc.Height               = 1;
    desc.Format               = DXGI_FORMAT_R8G8B8A8_UNORM;
    desc.MipLevels            = 1;
    desc.ArraySize            = 1;
    desc.SampleDesc.Count     = 1;
    desc.Usage                = D3D11_USAGE_DEFAULT;
    desc.BindFlags            = D3D11_BIND_SHADER_RESOURCE;

    constexpr uint8_t texData[] = {0x10, 0x20, 0x30, 0x40};
    D3D11_SUBRESOURCE_DATA subres;
    subres.pSysMem     = texData;
    subres.SysMemPitch = 4;

    ID3D11Texture2D *texture = nullptr;
    (void)mD3D->CreateTexture2D(&desc, &subres, &texture);
    ASSERT_NE(nullptr, texture);

    result = eglStreamPostD3DTextureANGLE(mDisplay, mStream, (void *)texture, nullptr);
    ASSERT_EGL_TRUE(result);
    ASSERT_EGL_SUCCESS();

    // Sample and test

    result = eglStreamConsumerAcquireKHR(mDisplay, mStream);
    ASSERT_EGL_TRUE(result);
    ASSERT_EGL_SUCCESS();

    drawQuad(prog, "aPos", 0.0);

    uint32_t pixel;
    glReadPixels(0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, &pixel);
    ASSERT_GL_NO_ERROR();
    ASSERT_EQ(pixel, static_cast<uint32_t>(0x40302010));

    result = eglStreamConsumerReleaseKHR(mDisplay, mStream);
    ASSERT_EGL_TRUE(result);
    ASSERT_EGL_SUCCESS();
}

// Test NV12 texture sampling via EGLStreams
TEST_P(D3D11TextureStreamSamplingTest, NV12)
{
    EGLWindow *window  = getEGLWindow();
    EGLDisplay display = window->getDisplay();
    ANGLE_SKIP_TEST_IF(
        !eglDisplayExtensionEnabled(display, "EGL_ANGLE_stream_producer_d3d_texture"));
    ANGLE_SKIP_TEST_IF(!CheckNV12TextureSupport(mD3D));

    constexpr char kVertShader[] = R"(
        attribute vec4 aPos;
        varying vec2 vTexCoord;
        void main()
        {
            gl_Position = aPos;
            vTexCoord = gl_Position.xy * 0.5 + 0.5;
        }
    )";
    const char kFragShader[]     = R"(
        #extension GL_NV_EGL_stream_consumer_external : require
        precision mediump float;
        uniform samplerExternalOES uTexY;
        uniform samplerExternalOES uTexUV;
        varying vec2 vTexCoord;
        void main()
        {
            gl_FragColor.r = texture2D(uTexY, vTexCoord).r;
            gl_FragColor.gb = texture2D(uTexUV, vTexCoord).rg;
            gl_FragColor.a = 1.0;
        }
    )";
    ANGLE_GL_PROGRAM(prog, kVertShader, kFragShader);
    glUseProgram(prog);
    glUniform1i(glGetUniformLocation(prog, "uTexY"), 0);
    glUniform1i(glGetUniformLocation(prog, "uTexUV"), 1);

    ASSERT_GL_NO_ERROR();

    GLTexture tex[2];
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_EXTERNAL_OES, tex[0]);
    glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    ASSERT_GL_NO_ERROR();
    glActiveTexture(GL_TEXTURE1);
    glBindTexture(GL_TEXTURE_EXTERNAL_OES, tex[1]);
    glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    ASSERT_GL_NO_ERROR();

    glActiveTexture(GL_TEXTURE3);  // Set to an unused slot to ensure StreamConsumer picks up
                                   // the current slot.
    constexpr EGLAttrib consumerAttributes[] = {
        EGL_COLOR_BUFFER_TYPE,
        EGL_YUV_BUFFER_EXT,
        EGL_YUV_NUMBER_OF_PLANES_EXT,
        2,
        EGL_YUV_PLANE0_TEXTURE_UNIT_NV,
        0,
        EGL_YUV_PLANE1_TEXTURE_UNIT_NV,
        1,
        EGL_NONE,
    };
    EGLBoolean result = eglStreamConsumerGLTextureExternalAttribsNV(
        mDisplay, mStream, const_cast<EGLAttrib *>(consumerAttributes));
    ASSERT_EGL_TRUE(result);
    ASSERT_EGL_SUCCESS();

    result = eglCreateStreamProducerD3DTextureANGLE(mDisplay, mStream, nullptr);
    ASSERT_EGL_TRUE(result);
    ASSERT_EGL_SUCCESS();

    // Create and post the d3d11 texture

    D3D11_TEXTURE2D_DESC desc = {};
    desc.Width                = 2;
    desc.Height               = 2;
    desc.Format               = DXGI_FORMAT_NV12;
    desc.MipLevels            = 1;
    desc.ArraySize            = 1;
    desc.SampleDesc.Count     = 1;
    desc.Usage                = D3D11_USAGE_DEFAULT;
    desc.BindFlags            = D3D11_BIND_SHADER_RESOURCE;

    /* DXGI_FORMAT_NV12:
     * Width and height must be even.
     * Direct3D 11 staging resources and initData parameters for this format use
     * (rowPitch * (height + (height / 2))) bytes.
     * The first (SysMemPitch * height) bytes are the Y plane, the remaining
     * (SysMemPitch * (height / 2)) bytes are the UV plane.
     */
    constexpr uint8_t texData[] = {0x10, 0x20, 0x30, 0x40, 0x50, 0x60};
    D3D11_SUBRESOURCE_DATA subres;
    subres.pSysMem     = texData;
    subres.SysMemPitch = 2;

    ID3D11Texture2D *texture = nullptr;
    (void)mD3D->CreateTexture2D(&desc, &subres, &texture);
    ASSERT_NE(nullptr, texture);

    result = eglStreamPostD3DTextureANGLE(mDisplay, mStream, (void *)texture, nullptr);
    ASSERT_EGL_TRUE(result);
    ASSERT_EGL_SUCCESS();

    // Sample and test

    result = eglStreamConsumerAcquireKHR(mDisplay, mStream);
    ASSERT_EGL_TRUE(result);
    ASSERT_EGL_SUCCESS();

    drawQuad(prog, "aPos", 0.0);
    uint32_t pixel;
    glReadPixels(0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, &pixel);
    ASSERT_EQ(pixel, 0xff605010);

    glReadPixels(1, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, &pixel);
    ASSERT_EQ(pixel, 0xff605020);

    glReadPixels(0, 1, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, &pixel);
    ASSERT_EQ(pixel, 0xff605030);

    glReadPixels(1, 1, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, &pixel);
    ASSERT_EQ(pixel, 0xff605040);

    result = eglStreamConsumerReleaseKHR(mDisplay, mStream);
    ASSERT_EGL_TRUE(result);
    ASSERT_EGL_SUCCESS();
}

// End2end test for rendering an NV12 texture. Renders a YUV quad, reads back the RGB values, and
// ensures they are correct
TEST_P(EGLStreamTest, StreamProducerTextureNV12End2End)
{
    EGLWindow *window  = getEGLWindow();
    EGLDisplay display = window->getDisplay();
    ANGLE_SKIP_TEST_IF(
        !eglDisplayExtensionEnabled(display, "EGL_ANGLE_stream_producer_d3d_texture"));

    bool useESSL3Shaders =
        getClientMajorVersion() >= 3 && extensionEnabled("GL_OES_EGL_image_external_essl3");

    // yuv to rgb conversion shader using Microsoft's given conversion formulas
    const char *yuvVS = nullptr;
    const char *yuvPS = nullptr;
    if (useESSL3Shaders)
    {
        yuvVS =
            "#version 300 es\n"
            "in highp vec4 position;\n"
            "out vec2 texcoord;\n"
            "void main(void)\n"
            "{\n"
            "    gl_Position = position;\n"
            "    texcoord = (position.xy * 0.5) + 0.5;\n"
            "    texcoord.y = 1.0 - texcoord.y;\n"
            "}\n";
        yuvPS =
            "#version 300 es\n"
            "#extension GL_OES_EGL_image_external_essl3 : require\n"
            "#extension GL_NV_EGL_stream_consumer_external : require\n"
            "precision highp float;\n"
            "in vec2 texcoord;\n"
            "out vec4 color;\n"
            "uniform samplerExternalOES y;\n"
            "uniform samplerExternalOES uv\n;"
            "void main(void)\n"
            "{\n"
            "    float c = texture(y, texcoord).r - (16.0 / 256.0);\n"
            "    float d = texture(uv, texcoord).r - 0.5;\n"
            "    float e = texture(uv, texcoord).g - 0.5;\n"
            "    float r = 1.164383 * c + 1.596027 * e;\n"
            "    float g = 1.164383 * c - 0.391762 * d - 0.812968 * e;\n"
            "    float b = 1.164383 * c + 2.017232 * d;\n"
            "    color = vec4(r, g, b, 1.0);\n"
            "}\n";
    }
    else
    {
        yuvVS =
            "attribute highp vec4 position;\n"
            "varying vec2 texcoord;\n"
            "void main(void)\n"
            "{\n"
            "    gl_Position = position;\n"
            "    texcoord = (position.xy * 0.5) + 0.5;\n"
            "    texcoord.y = 1.0 - texcoord.y;\n"
            "}\n";

        yuvPS =
            "#extension GL_NV_EGL_stream_consumer_external : require\n"
            "precision highp float;\n"
            "varying vec2 texcoord;\n"
            "uniform samplerExternalOES y;\n"
            "uniform samplerExternalOES uv\n;"
            "void main(void)\n"
            "{\n"
            "    float c = texture2D(y, texcoord).r - (16.0 / 256.0);\n"
            "    float d = texture2D(uv, texcoord).r - 0.5;\n"
            "    float e = texture2D(uv, texcoord).g - 0.5;\n"
            "    float r = 1.164383 * c + 1.596027 * e;\n"
            "    float g = 1.164383 * c - 0.391762 * d - 0.812968 * e;\n"
            "    float b = 1.164383 * c + 2.017232 * d;\n"
            "    gl_FragColor = vec4(r, g, b, 1.0);\n"
            "}\n";
    }

    ANGLE_GL_PROGRAM(program, yuvVS, yuvPS);
    GLuint yUniform  = glGetUniformLocation(program, "y");
    GLuint uvUniform = glGetUniformLocation(program, "uv");

    // Fetch the D3D11 device
    EGLDeviceEXT eglDevice;
    eglQueryDisplayAttribEXT(display, EGL_DEVICE_EXT, (EGLAttrib *)&eglDevice);
    ID3D11Device *device;
    eglQueryDeviceAttribEXT(eglDevice, EGL_D3D11_DEVICE_ANGLE, (EGLAttrib *)&device);

    ANGLE_SKIP_TEST_IF(!CheckNV12TextureSupport(device));

    // Create the NV12 D3D11 texture
    D3D11_TEXTURE2D_DESC desc;
    desc.Width              = yuvtest_width;
    desc.Height             = yuvtest_height;
    desc.Format             = DXGI_FORMAT_NV12;
    desc.MipLevels          = 1;
    desc.ArraySize          = 1;
    desc.SampleDesc.Count   = 1;
    desc.SampleDesc.Quality = 0;
    desc.Usage              = D3D11_USAGE_DEFAULT;
    desc.BindFlags          = D3D11_BIND_SHADER_RESOURCE;
    desc.CPUAccessFlags     = 0;
    desc.MiscFlags          = 0;

    D3D11_SUBRESOURCE_DATA subres;
    subres.pSysMem          = yuvtest_data;
    subres.SysMemPitch      = yuvtest_width;
    subres.SysMemSlicePitch = yuvtest_width * yuvtest_height * 3 / 2;

    ID3D11Texture2D *texture = nullptr;
    (void)device->CreateTexture2D(&desc, &subres, &texture);
    ASSERT_NE(nullptr, texture);

    // Create the stream
    const EGLint streamAttributes[] = {
        EGL_CONSUMER_LATENCY_USEC_KHR, 0, EGL_CONSUMER_ACQUIRE_TIMEOUT_USEC_KHR, 0, EGL_NONE,
    };

    EGLStreamKHR stream = eglCreateStreamKHR(display, streamAttributes);
    ASSERT_EGL_SUCCESS();

    EGLAttrib consumerAttributes[] = {
        EGL_COLOR_BUFFER_TYPE,
        EGL_YUV_BUFFER_EXT,
        EGL_YUV_NUMBER_OF_PLANES_EXT,
        2,
        EGL_YUV_PLANE0_TEXTURE_UNIT_NV,
        0,
        EGL_YUV_PLANE1_TEXTURE_UNIT_NV,
        1,
        EGL_NONE,
    };

    GLuint tex[2];
    glGenTextures(2, tex);
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_EXTERNAL_OES, tex[0]);
    glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    ASSERT_GL_NO_ERROR();
    glActiveTexture(GL_TEXTURE1);
    glBindTexture(GL_TEXTURE_EXTERNAL_OES, tex[1]);
    glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    ASSERT_GL_NO_ERROR();

    EGLBoolean result =
        eglStreamConsumerGLTextureExternalAttribsNV(display, stream, consumerAttributes);
    ASSERT_EGL_TRUE(result);
    ASSERT_EGL_SUCCESS();

    EGLAttrib producerAttributes[] = {
        EGL_NONE,
    };

    result = eglCreateStreamProducerD3DTextureANGLE(display, stream, producerAttributes);
    ASSERT_EGL_TRUE(result);
    ASSERT_EGL_SUCCESS();

    // Insert the frame
    EGLAttrib frameAttributes[] = {
        EGL_D3D_TEXTURE_SUBRESOURCE_ID_ANGLE,
        0,
        EGL_NONE,
    };
    result = eglStreamPostD3DTextureANGLE(display, stream, (void *)texture, frameAttributes);
    ASSERT_EGL_TRUE(result);
    ASSERT_EGL_SUCCESS();

    EGLint state;
    eglQueryStreamKHR(display, stream, EGL_STREAM_STATE_KHR, &state);
    ASSERT_EGL_SUCCESS();
    ASSERT_EQ(EGL_STREAM_STATE_NEW_FRAME_AVAILABLE_KHR, state);

    eglStreamConsumerAcquireKHR(display, stream);
    ASSERT_EGL_SUCCESS();

    glUseProgram(program);
    glUniform1i(yUniform, 0);
    glUniform1i(uvUniform, 1);
    drawQuad(program, "position", 0.0f);
    ASSERT_GL_NO_ERROR();

    eglStreamConsumerReleaseKHR(display, stream);
    ASSERT_EGL_SUCCESS();

    eglSwapBuffers(display, window->getSurface());
    SafeRelease(texture);
}

ANGLE_INSTANTIATE_TEST(EGLStreamTest,
                       ES2_D3D9(),
                       ES2_D3D11(),
                       ES3_D3D11(),
                       ES2_OPENGL(),
                       ES3_OPENGL(),
                       ES2_VULKAN());
ANGLE_INSTANTIATE_TEST(D3D11TextureStreamSamplingTest, ES2_D3D11(), ES3_D3D11());
}  // anonymous namespace
