//
// Copyright 2018 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.
//
// VulkanPipelineCachePerf:
//   Performance benchmark for the Vulkan Pipeline cache.
//

#include "ANGLEPerfTest.h"
#include "common/unsafe_buffers.h"

#include "libANGLE/renderer/vulkan/vk_cache_utils.h"
#include "libANGLE/renderer/vulkan/vk_helpers.h"
#include "libANGLE/renderer/vulkan/vk_renderer.h"
#include "util/random_utils.h"

using namespace rx;

namespace
{
constexpr unsigned int kIterationsPerStep = 100;

struct Params
{
    bool withDynamicState = false;
};

class VulkanPipelineCachePerfTest : public ANGLEPerfTest,
                                    public ::testing::WithParamInterface<Params>
{
  public:
    VulkanPipelineCachePerfTest();
    ~VulkanPipelineCachePerfTest();

    void SetUp() override;
    void step() override;

    GraphicsPipelineCache<GraphicsPipelineDescCompleteHash> mCache;
    angle::RNG mRNG;

    std::vector<vk::GraphicsPipelineDesc> mCacheHits;
    std::vector<vk::GraphicsPipelineDesc> mCacheMisses;
    size_t mMissIndex = 0;

  private:
    void randomizeDesc(vk::GraphicsPipelineDesc *desc);
};

VulkanPipelineCachePerfTest::VulkanPipelineCachePerfTest()
    : ANGLEPerfTest("VulkanPipelineCachePerf", "", "", kIterationsPerStep), mRNG(0x12345678u)
{}

VulkanPipelineCachePerfTest::~VulkanPipelineCachePerfTest()
{
    mCache.reset();
}

void VulkanPipelineCachePerfTest::SetUp()
{
    ANGLEPerfTest::SetUp();

    // Insert a number of random pipeline states.
    for (int pipelineCount = 0; pipelineCount < 100; ++pipelineCount)
    {
        vk::Pipeline pipeline;
        vk::GraphicsPipelineDesc desc;
        randomizeDesc(&desc);

        if (pipelineCount < 10)
        {
            mCacheHits.push_back(desc);
        }
        mCache.populate(desc, std::move(pipeline), nullptr);
    }

    for (int missCount = 0; missCount < 10000; ++missCount)
    {
        vk::GraphicsPipelineDesc desc;
        randomizeDesc(&desc);
        mCacheMisses.push_back(desc);
    }
}

void VulkanPipelineCachePerfTest::randomizeDesc(vk::GraphicsPipelineDesc *desc)
{
    std::vector<uint8_t> bytes(sizeof(vk::GraphicsPipelineDesc));
    FillVectorWithRandomUBytes(&mRNG, &bytes);
    ANGLE_UNSAFE_TODO(memcpy(desc, bytes.data(), sizeof(vk::GraphicsPipelineDesc)));

    desc->setSupportsDynamicStateForTest(GetParam().withDynamicState);
}

void VulkanPipelineCachePerfTest::step()
{
    vk::RenderPass rp;
    vk::PipelineLayout pl;
    vk::PipelineCache pc;
    vk::PipelineCacheAccess spc;
    vk::ShaderModulePtr vs = vk::ShaderModulePtr::MakeShared(VK_NULL_HANDLE);
    vk::ShaderModulePtr fs = vk::ShaderModulePtr::MakeShared(VK_NULL_HANDLE);
    vk::ShaderModuleMap ssm;
    const vk::GraphicsPipelineDesc *desc = nullptr;
    vk::PipelineHelper *result           = nullptr;

    // The Vulkan handle types are difficult to cast to without #ifdefs.
    vs->setHandle((VkShaderModule)1);
    fs->setHandle((VkShaderModule)2);

    ssm[gl::ShaderType::Vertex]   = vs;
    ssm[gl::ShaderType::Fragment] = fs;

    spc.init(&pc, nullptr);

    for (unsigned int iteration = 0; iteration < kIterationsPerStep; ++iteration)
    {
        for (const auto &hit : mCacheHits)
        {
            if (!mCache.getPipeline(hit, &desc, &result))
            {
                (void)mCache.createPipeline(VK_NULL_HANDLE, &spc, rp, pl, {&ssm},
                                            PipelineSource::Draw, hit, &desc, &result);
            }
        }
    }

    for (int missCount = 0; missCount < 20 && mMissIndex < mCacheMisses.size();
         ++missCount, ++mMissIndex)
    {
        const auto &miss = mCacheMisses[mMissIndex];
        if (!mCache.getPipeline(miss, &desc, &result))
        {
            (void)mCache.createPipeline(VK_NULL_HANDLE, &spc, rp, pl, {&ssm}, PipelineSource::Draw,
                                        miss, &desc, &result);
        }
    }

    vs->setHandle(VK_NULL_HANDLE);
    fs->setHandle(VK_NULL_HANDLE);
}

}  // anonymous namespace

// Test performance of pipeline hash and look up in Vulkan
TEST_P(VulkanPipelineCachePerfTest, Run)
{
    run();
}

INSTANTIATE_TEST_SUITE_P(,
                         VulkanPipelineCachePerfTest,
                         ::testing::ValuesIn(std::vector<Params>{{Params{false}, Params{true}}}));
