/*
 * Copyright 2020 Hans-Kristian Arntzen for Valve Corporation
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
 */

#define VKD3D_DBG_CHANNEL VKD3D_DBG_CHANNEL_API

#define INITGUID
#define VKD3D_TEST_DECLARE_MAIN
#include "d3d12_crosstest.h"

static void setup(int argc, char **argv)
{
    pfn_D3D12CreateDevice = get_d3d12_pfn(D3D12CreateDevice);
    pfn_D3D12EnableExperimentalFeatures = get_d3d12_pfn(D3D12EnableExperimentalFeatures);
    pfn_D3D12GetDebugInterface = get_d3d12_pfn(D3D12GetDebugInterface);

    parse_args(argc, argv);
    enable_d3d12_debug_layer(argc, argv);
    init_adapter_info();

    pfn_D3D12CreateVersionedRootSignatureDeserializer = get_d3d12_pfn(D3D12CreateVersionedRootSignatureDeserializer);
    pfn_D3D12SerializeVersionedRootSignature = get_d3d12_pfn(D3D12SerializeVersionedRootSignature);
}

static double get_time(void)
{
#ifdef _WIN32
    LARGE_INTEGER lc, lf;
    QueryPerformanceCounter(&lc);
    QueryPerformanceFrequency(&lf);
    return (double)lc.QuadPart / (double)lf.QuadPart;
#else
    struct timespec ts;
    clock_gettime(CLOCK_MONOTONIC_RAW, &ts);
    return ts.tv_sec + 1e-9 * ts.tv_nsec;
#endif
}

static void fill_descriptor_heap_srv(ID3D12Device *device, ID3D12DescriptorHeap *heap,
        ID3D12Resource *resource, const D3D12_SHADER_RESOURCE_VIEW_DESC *desc, unsigned int count)
{
    D3D12_CPU_DESCRIPTOR_HANDLE cpu_handle;
    UINT stride, i;

    stride = ID3D12Device_GetDescriptorHandleIncrementSize(device, D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);
    cpu_handle = ID3D12DescriptorHeap_GetCPUDescriptorHandleForHeapStart(heap);
    for (i = 0; i < count; i++)
    {
        ID3D12Device_CreateShaderResourceView(device, resource, desc, cpu_handle);
        cpu_handle.ptr += stride;
    }
}

static void copy_descriptor_heap(ID3D12Device *device, ID3D12DescriptorHeap *gpu_heap,
        ID3D12DescriptorHeap *cpu_heap, unsigned int count)
{
    ID3D12Device_CopyDescriptorsSimple(device, count,
            ID3D12DescriptorHeap_GetCPUDescriptorHandleForHeapStart(gpu_heap),
            ID3D12DescriptorHeap_GetCPUDescriptorHandleForHeapStart(cpu_heap),
            D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);
}

static void copy_descriptor_heap_single(ID3D12Device *device, ID3D12DescriptorHeap *gpu_heap,
        ID3D12DescriptorHeap *cpu_heap, unsigned int count)
{
    D3D12_CPU_DESCRIPTOR_HANDLE gpu;
    D3D12_CPU_DESCRIPTOR_HANDLE cpu;
    UINT64 increment;
    unsigned int i;

    gpu = ID3D12DescriptorHeap_GetCPUDescriptorHandleForHeapStart(gpu_heap);
    cpu = ID3D12DescriptorHeap_GetCPUDescriptorHandleForHeapStart(cpu_heap);
    increment = ID3D12Device_GetDescriptorHandleIncrementSize(device, D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);

    for (i = 0; i < count; i++)
    {
        ID3D12Device_CopyDescriptorsSimple(device, 1, gpu, cpu,
                D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);
        gpu.ptr += increment;
        cpu.ptr += increment;
    }
}

static void zero_descriptor_heap(ID3D12Device *device, ID3D12DescriptorHeap *heap, unsigned int count)
{
    D3D12_SHADER_RESOURCE_VIEW_DESC srv_desc;
    memset(&srv_desc, 0, sizeof(srv_desc));

    srv_desc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;
    srv_desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
    srv_desc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
    srv_desc.Texture2D.MipLevels = 1;

    fill_descriptor_heap_srv(device, heap, NULL, &srv_desc, count);
}

static void do_benchmark_run(ID3D12Device *device)
{
    D3D12_SHADER_RESOURCE_VIEW_DESC srv_desc;
    D3D12_DESCRIPTOR_HEAP_DESC heap_desc;
    ID3D12DescriptorHeap *gpu_heap;
    ID3D12DescriptorHeap *cpu_heap;
    double start_time, end_time;
    ID3D12Resource *texture;
    HRESULT hr;

    heap_desc.NumDescriptors = 1000000;
    heap_desc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
    heap_desc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV;
    heap_desc.NodeMask = 0;
    hr = ID3D12Device_CreateDescriptorHeap(device, &heap_desc, &IID_ID3D12DescriptorHeap, (void**)&cpu_heap);
    ok(SUCCEEDED(hr), "Failed to create descriptor heap, hr #%x.\n", hr);

    heap_desc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;
    hr = ID3D12Device_CreateDescriptorHeap(device, &heap_desc, &IID_ID3D12DescriptorHeap, (void**)&gpu_heap);
    ok(SUCCEEDED(hr), "Failed to create descriptor heap, hr #%x.\n", hr);

    texture = create_default_texture2d(device,
                                       256, 256, 1, 1, DXGI_FORMAT_R8G8B8A8_UNORM,
                                       D3D12_RESOURCE_FLAG_NONE, D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE);
    ok(texture != NULL, "Failed to create texture.\n");

    srv_desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
    srv_desc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;
    srv_desc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
    srv_desc.Texture2D.MipLevels = 1;
    srv_desc.Texture2D.MostDetailedMip = 0;
    srv_desc.Texture2D.PlaneSlice = 0;
    srv_desc.Texture2D.ResourceMinLODClamp = 0.0f;

    /* Benchmark creation of 1 million SRVs in CPU-only heaps. */
    {
        start_time = get_time();
        fill_descriptor_heap_srv(device, cpu_heap, texture, &srv_desc, 1000000);
        end_time = get_time();
        printf("Creating 1M SRVs on blank CPU heap took: %.3f ms.\n", 1e3 * (end_time - start_time));
    }

    /* Do the same thing again, but this time on a used heap, so we also have to destroy existing views. */
    {
        start_time = get_time();
        fill_descriptor_heap_srv(device, cpu_heap, texture, &srv_desc, 1000000);
        end_time = get_time();
        printf("Creating 1M SRVs on dirty CPU heap took: %.3f ms.\n", 1e3 * (end_time - start_time));
    }

    /* Fill shader visible heaps */
    {
        start_time = get_time();
        fill_descriptor_heap_srv(device, gpu_heap, texture, &srv_desc, 1000000);
        end_time = get_time();
        printf("Creating 1M SRVs on blank GPU-visible heap took: %.3f ms.\n", 1e3 * (end_time - start_time));
    }

    /* Do the same thing again, but this time on a used heap, so we also have to destroy existing views. */
    {
        start_time = get_time();
        fill_descriptor_heap_srv(device, gpu_heap, texture, &srv_desc, 1000000);
        end_time = get_time();
        printf("Creating 1M SRVs on dirty GPU-visible heap took: %.3f ms.\n", 1e3 * (end_time - start_time));
    }

    /* Try copying descriptors */
    {
        start_time = get_time();
        copy_descriptor_heap(device, gpu_heap, cpu_heap, 1000000);
        end_time = get_time();
        printf("Copying 1M SRVs to dirty GPU visible heap took: %.3f ms.\n", 1e3 * (end_time - start_time));
    }

    /* Try copying descriptors with duplication */
    {
        start_time = get_time();
        copy_descriptor_heap(device, gpu_heap, cpu_heap, 1000000);
        end_time = get_time();
        printf("Copying 1M SRVs (duplicates) took: %.3f ms.\n", 1e3 * (end_time - start_time));
    }

    /* Create zero descriptors. */
    {
        start_time = get_time();
        zero_descriptor_heap(device, gpu_heap, 1000000);
        end_time = get_time();
        printf("Creating 1M null-SRVs took: %.3f ms.\n", 1e3 * (end_time - start_time));
    }

    /* Try copying descriptors on top of zero-initialized descriptor heap. */
    {
        start_time = get_time();
        copy_descriptor_heap(device, gpu_heap, cpu_heap, 1000000);
        end_time = get_time();
        printf("Copying 1M SRVs to zeroed GPU visible heap took: %.3f ms.\n", 1e3 * (end_time - start_time));
    }

    /* Try copying descriptors one at a time on top of zero-initialized descriptor heap. */
    {
        start_time = get_time();
        copy_descriptor_heap_single(device, gpu_heap, cpu_heap, 1000000);
        end_time = get_time();
        printf("Copying 1M individual SRVs to GPU visible heap (duplicates) took: %.3f ms.\n", 1e3 * (end_time - start_time));
    }

    /* Create zero descriptors. */
    zero_descriptor_heap(device, gpu_heap, 1000000);

    {
        start_time = get_time();
        copy_descriptor_heap_single(device, gpu_heap, cpu_heap, 1000000);
        end_time = get_time();
        printf("Copying 1M individual SRVs to zeroed GPU visible heap took: %.3f ms.\n", 1e3 * (end_time - start_time));
    }

    ID3D12Resource_Release(texture);
    ID3D12DescriptorHeap_Release(cpu_heap);
    ID3D12DescriptorHeap_Release(gpu_heap);
}

START_TEST(descriptor_performance)
{
    ID3D12Device *device;
    unsigned int i;

    setup(argc, argv);
    device = create_device();
    ok(device != NULL, "Failed to create device.\n");

    for (i = 0; i < 100; i++)
        do_benchmark_run(device);

    ID3D12Device_Release(device);
}

