//
// Copyright 2015 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.
//
// D3D11EmulatedIndexedBufferTest:
//   Tests to validate our D3D11 support for emulating an indexed
//   vertex buffer.
//

#include "libANGLE/Context.h"
#include "libANGLE/angletypes.h"
#include "libANGLE/renderer/d3d/IndexDataManager.h"
#include "libANGLE/renderer/d3d/d3d11/Buffer11.h"
#include "libANGLE/renderer/d3d/d3d11/Context11.h"
#include "libANGLE/renderer/d3d/d3d11/Renderer11.h"
#include "test_utils/ANGLETest.h"
#include "test_utils/angle_test_instantiate.h"

using namespace angle;

namespace
{

class D3D11EmulatedIndexedBufferTest : public ANGLETest
{
  protected:
    void SetUp() override
    {
        ANGLETest::SetUp();
        ASSERT_EQ(EGL_PLATFORM_ANGLE_TYPE_D3D11_ANGLE, GetParam().getRenderer());

        mContext                 = static_cast<gl::Context *>(getEGLWindow()->getContext());
        rx::Context11 *context11 = rx::GetImplAs<rx::Context11>(mContext);
        mRenderer                = context11->getRenderer();

        mSourceBuffer       = new rx::Buffer11(mBufferState, mRenderer);
        GLfloat testData[]  = {1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f};
        angle::Result error = mSourceBuffer->setData(nullptr, gl::BufferBinding::Array, testData,
                                                     sizeof(testData), gl::BufferUsage::StaticDraw);
        ASSERT_EQ(angle::Result::Continue(), error);

        mTranslatedAttribute.baseOffset            = 0;
        mTranslatedAttribute.usesFirstVertexOffset = false;
        mTranslatedAttribute.stride                = sizeof(GLfloat);

        GLubyte indices[] = {0, 0, 3, 4, 2, 1, 1};

        for (size_t i = 0; i < ArraySize(indices); i++)
        {
            mExpectedExpandedData.push_back(testData[indices[i]]);
            mubyteIndices.push_back(indices[i]);
            muintIndices.push_back(indices[i]);
            mushortIndices.push_back(indices[i]);
        }
    }

    void TearDown() override
    {
        SafeDelete(mSourceBuffer);
        ANGLETest::TearDown();
    }

    void createMappableCompareBufferFromEmulatedBuffer(ID3D11Buffer *sourceBuffer,
                                                       GLuint size,
                                                       ID3D11Buffer **mappableBuffer)
    {
        *mappableBuffer = nullptr;

        D3D11_BUFFER_DESC bufferDesc;
        bufferDesc.ByteWidth           = size;
        bufferDesc.MiscFlags           = 0;
        bufferDesc.StructureByteStride = 0;
        bufferDesc.Usage               = D3D11_USAGE_STAGING;
        bufferDesc.BindFlags           = 0;
        bufferDesc.CPUAccessFlags      = D3D11_CPU_ACCESS_READ | D3D11_CPU_ACCESS_WRITE;

        HRESULT hr = mRenderer->getDevice()->CreateBuffer(&bufferDesc, nullptr, mappableBuffer);
        ASSERT_TRUE(SUCCEEDED(hr));

        D3D11_BOX srcBox;
        srcBox.left   = 0;
        srcBox.right  = size;
        srcBox.top    = 0;
        srcBox.bottom = 1;
        srcBox.front  = 0;
        srcBox.back   = 1;

        mRenderer->getDeviceContext()->CopySubresourceRegion(*mappableBuffer, 0, 0, 0, 0,
                                                             sourceBuffer, 0, &srcBox);
    }

    void compareContents(ID3D11Buffer *actual)
    {
        ID3D11Buffer *compareBuffer = nullptr;
        createMappableCompareBufferFromEmulatedBuffer(
            actual, sizeof(GLfloat) * static_cast<GLuint>(mExpectedExpandedData.size()),
            &compareBuffer);

        D3D11_MAPPED_SUBRESOURCE mappedResource;
        HRESULT hr = mRenderer->getDeviceContext()->Map(compareBuffer, 0, D3D11_MAP_READ, 0,
                                                        &mappedResource);
        ASSERT_TRUE(SUCCEEDED(hr));

        GLfloat *compareData = static_cast<GLfloat *>(mappedResource.pData);
        for (size_t i = 0; i < mExpectedExpandedData.size(); i++)
        {
            EXPECT_EQ(mExpectedExpandedData[i], compareData[i]);
        }

        mRenderer->getDeviceContext()->Unmap(compareBuffer, 0);
        SafeRelease(compareBuffer);
    }

    void emulateAndCompare(rx::SourceIndexData *srcData)
    {
        ID3D11Buffer *emulatedBuffer = nullptr;
        angle::Result error          = mSourceBuffer->getEmulatedIndexedBuffer(
            mContext, srcData, mTranslatedAttribute, 0, &emulatedBuffer);
        ASSERT_EQ(angle::Result::Continue(), error);
        ASSERT_TRUE(emulatedBuffer != nullptr);
        compareContents(emulatedBuffer);
    }

  protected:
    gl::Context *mContext;
    rx::Buffer11 *mSourceBuffer;
    rx::Renderer11 *mRenderer;
    rx::TranslatedAttribute mTranslatedAttribute;
    std::vector<GLfloat> mExpectedExpandedData;
    std::vector<GLubyte> mubyteIndices;
    std::vector<GLuint> muintIndices;
    std::vector<GLushort> mushortIndices;
    gl::BufferState mBufferState;
};

// This tests that a GL_UNSIGNED_BYTE indices list can be successfully expanded
// into a valid emulated indexed buffer.
TEST_P(D3D11EmulatedIndexedBufferTest, TestNativeToExpandedUsingGLubyteIndices)
{
    rx::SourceIndexData srcData = {nullptr, mubyteIndices.data(),
                                   static_cast<unsigned int>(mubyteIndices.size()),
                                   gl::DrawElementsType::UnsignedByte, false};
    emulateAndCompare(&srcData);
}

// This tests that a GL_UNSIGNED_SHORT indices list can be successfully expanded
// into a valid emulated indexed buffer.
TEST_P(D3D11EmulatedIndexedBufferTest, TestNativeToExpandedUsingGLushortIndices)
{
    rx::SourceIndexData srcData = {nullptr, mushortIndices.data(),
                                   static_cast<unsigned int>(mushortIndices.size()),
                                   gl::DrawElementsType::UnsignedShort, false};
    emulateAndCompare(&srcData);
}

// This tests that a GL_UNSIGNED_INT indices list can be successfully expanded
// into a valid emulated indexed buffer.
TEST_P(D3D11EmulatedIndexedBufferTest, TestNativeToExpandedUsingGLuintIndices)
{
    rx::SourceIndexData srcData = {nullptr, muintIndices.data(),
                                   static_cast<unsigned int>(muintIndices.size()),
                                   gl::DrawElementsType::UnsignedInt, false};
    emulateAndCompare(&srcData);
}

// This tests verifies that a Buffer11 contents remain unchanged after calling
// getEmulatedIndexedBuffer
TEST_P(D3D11EmulatedIndexedBufferTest, TestSourceBufferRemainsUntouchedAfterExpandOperation)
{
    // Copy the original source buffer before any expand calls have been made
    gl::BufferState cleanSourceState;
    rx::Buffer11 *cleanSourceBuffer = new rx::Buffer11(cleanSourceState, mRenderer);
    ASSERT_FALSE(
        cleanSourceBuffer->copySubData(nullptr, mSourceBuffer, 0, 0, mSourceBuffer->getSize())
            .isError());

    // Do a basic exanded and compare test.
    rx::SourceIndexData srcData = {nullptr, muintIndices.data(),
                                   static_cast<unsigned int>(muintIndices.size()),
                                   gl::DrawElementsType::UnsignedInt, false};
    emulateAndCompare(&srcData);

    const uint8_t *sourceBufferMem = nullptr;
    const uint8_t *cleanBufferMem  = nullptr;

    angle::Result error = mSourceBuffer->getData(mContext, &sourceBufferMem);
    ASSERT_EQ(angle::Result::Continue(), error);

    error = cleanSourceBuffer->getData(mContext, &cleanBufferMem);
    ASSERT_FALSE(error.isError());

    int result = memcmp(sourceBufferMem, cleanBufferMem, cleanSourceBuffer->getSize());
    ASSERT_EQ(result, 0);

    SafeDelete(cleanSourceBuffer);
}

ANGLE_INSTANTIATE_TEST(D3D11EmulatedIndexedBufferTest, ES2_D3D11());

}  // anonymous namespace
