/*
 * Copyright 2016-2017 Józef Kucia for CodeWeavers
 * Copyright 2020-2021 Philip Rebohle for Valve Corporation
 * Copyright 2020-2021 Joshua Ashton for Valve Corporation
 * Copyright 2020-2021 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
#include "d3d12_crosstest.h"

void test_clock_calibration(void)
{
#ifndef _WIN32
    skip("Clock calibration tests cannot pass on native Linux. Skipping.\n");
#else
    uint64_t cpu_times[2], gpu_times[2];
    struct test_context context;
    HRESULT hr;

    if (!init_test_context(&context, NULL))
        return;

    hr = ID3D12CommandQueue_GetClockCalibration(context.queue, &gpu_times[0], &cpu_times[0]);
    ok(hr == S_OK, "Failed retrieve calibrated timestamps, hr %#x.\n", hr);

    vkd3d_sleep(100);

    hr = ID3D12CommandQueue_GetClockCalibration(context.queue, &gpu_times[1], &cpu_times[1]);
    ok(hr == S_OK, "Failed retrieve calibrated timestamps, hr %#x.\n", hr);

    ok(gpu_times[1] > gpu_times[0], "Inconsistent GPU timestamps.\n");
    ok(cpu_times[1] > cpu_times[0], "Inconsistent CPU timestamps.\n");

    destroy_test_context(&context);
#endif
}

void test_open_heap_from_address(void)
{
#ifdef _WIN32
    ID3D12Resource *readback_resource;
    struct test_context context;
    struct resource_readback rb;
    D3D12_HEAP_DESC heap_desc;
    ID3D12Resource *resource;
    unsigned int heap_size;
    ID3D12Device3 *device3;
    ID3D12Device *device;
    HANDLE file_handle;
    ID3D12Heap *heap;
    unsigned int i;
    uint32_t *addr;
    HRESULT hr;

    if (!init_test_context(&context, NULL))
        return;

    device = context.device;
    hr = ID3D12Device_QueryInterface(device, &IID_ID3D12Device3, (void **)&device3);
    ok(hr == S_OK, "Failed to query ID3D12Device3, hr #%x.\n", hr);

    /* Simple case, import directly from VirtualAlloc. */
    {
        heap_size = 64 * 1024;
        addr = VirtualAlloc(NULL, heap_size, MEM_COMMIT, PAGE_READWRITE);
        ok(!!addr, "Failed to VirtualAllocate.\n");

        for (i = 0; i < heap_size / sizeof(uint32_t); i++)
            addr[i] = i;

        hr = ID3D12Device3_OpenExistingHeapFromAddress(device3, addr, &IID_ID3D12Heap, (void **)&heap);
        ok(hr == S_OK, "Failed to open heap from address: hr #%x.\n", hr);

        if (heap)
        {
            heap_desc = ID3D12Heap_GetDesc(heap);
            ok(heap_desc.SizeInBytes == heap_size, "Expected heap size of %u, but got %u.\n", heap_size, (unsigned int)heap_desc.SizeInBytes);
            ok(!!(heap_desc.Flags & D3D12_HEAP_FLAG_SHARED_CROSS_ADAPTER), "Expected Heap desc to have SHARED_CROSS_ADAPTER flag set.\n");
            ok(!!(heap_desc.Flags & D3D12_HEAP_FLAG_SHARED), "Expected heap desc to have SHARED flag set.\n");

            resource = create_placed_buffer(device, heap, 0, heap_size, D3D12_RESOURCE_FLAG_ALLOW_CROSS_ADAPTER, D3D12_RESOURCE_STATE_COPY_SOURCE);
            ok(!!resource, "Failed to create resource.\n");
            readback_resource = create_default_buffer(device, heap_size, D3D12_RESOURCE_FLAG_NONE, D3D12_RESOURCE_STATE_COPY_DEST);
            ID3D12GraphicsCommandList_CopyResource(context.list, readback_resource, resource);
            transition_resource_state(context.list, readback_resource, D3D12_RESOURCE_STATE_COPY_DEST, D3D12_RESOURCE_STATE_COPY_SOURCE);
            get_buffer_readback_with_command_list(readback_resource, DXGI_FORMAT_UNKNOWN, &rb, context.queue, context.list);
            reset_command_list(context.list, context.allocator);
            ok(!memcmp(rb.data, addr, heap_size), "Expected exact copy.\n");
            release_resource_readback(&rb);
            ID3D12Heap_Release(heap);
            ID3D12Resource_Release(readback_resource);
            ID3D12Resource_Release(resource);
        }
        VirtualFree(addr, 0, MEM_RELEASE);
    }

    /* Import at offset, which should fail. */
    {
        heap_size = 64 * 1024;
        addr = VirtualAlloc(NULL, heap_size, MEM_COMMIT, PAGE_READWRITE);
        ok(!!addr, "Failed to VirtualAllocate.\n");
        hr = ID3D12Device3_OpenExistingHeapFromAddress(device3, addr + 1024, &IID_ID3D12Heap, (void **)&heap);
        ok(hr == E_INVALIDARG, "Should not be able to open heap at offset from VirtualAlloc.\n");
    }

    /* HANDLE variant. Kernel drivers might not support this (yet) since the file mapping might be backed by a file descriptor. */
    {
        heap_size = 256 * 1024;
        file_handle = CreateFileMappingA(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, heap_size, "foobar");
        ok(!!file_handle, "Failed to open file mapping.\n");

        addr = MapViewOfFile(file_handle, FILE_MAP_ALL_ACCESS, 0, 0, heap_size);
        ok(!!addr, "Failed to map view of file.\n");
        for (i = 0; i < heap_size / sizeof(uint32_t); i++)
            addr[i] = i;

        hr = ID3D12Device3_OpenExistingHeapFromFileMapping(device3, file_handle, &IID_ID3D12Heap, (void **)&heap);
        ok(hr == S_OK, "Failed to open heap from file mapping: hr #%x.\n", hr);

        if (SUCCEEDED(hr))
        {
            heap_desc = ID3D12Heap_GetDesc(heap);
            ok(heap_desc.SizeInBytes == heap_size, "Expected heap size of %u, but got %u.\n", heap_size, (unsigned int)heap_desc.SizeInBytes);
            ok(!!(heap_desc.Flags & D3D12_HEAP_FLAG_SHARED_CROSS_ADAPTER), "Expected Heap desc to have SHARED_CROSS_ADAPTER flag set.\n");
            ok(!!(heap_desc.Flags & D3D12_HEAP_FLAG_SHARED), "Expected heap desc to have SHARED flag set.\n");

            resource = create_placed_buffer(device, heap, 0, heap_size, D3D12_RESOURCE_FLAG_ALLOW_CROSS_ADAPTER, D3D12_RESOURCE_STATE_COPY_SOURCE);
            ok(!!resource, "Failed to create resource.\n");
            readback_resource = create_default_buffer(device, heap_size, D3D12_RESOURCE_FLAG_NONE, D3D12_RESOURCE_STATE_COPY_DEST);
            ID3D12GraphicsCommandList_CopyResource(context.list, readback_resource, resource);
            transition_resource_state(context.list, readback_resource, D3D12_RESOURCE_STATE_COPY_DEST, D3D12_RESOURCE_STATE_COPY_SOURCE);
            get_buffer_readback_with_command_list(readback_resource, DXGI_FORMAT_UNKNOWN, &rb, context.queue, context.list);
            reset_command_list(context.list, context.allocator);
            for (i = 1; i < heap_size / sizeof(uint32_t); i++)
            {
                uint32_t v = get_readback_uint(&rb, i, 0, 0);
                todo ok(v == i, "Expected %u, got %u.\n", i, v);
                if (v != i)
                    break;
            }
            release_resource_readback(&rb);
            ID3D12Heap_Release(heap);
            ID3D12Resource_Release(readback_resource);
            ID3D12Resource_Release(resource);
        }

        UnmapViewOfFile(addr);
        CloseHandle(file_handle);
    }

    ID3D12Device3_Release(device3);
    destroy_test_context(&context);
#else
    skip("Cannot test OpenExistingHeapFrom* on non-native Win32 platforms.\n");
#endif
}

void test_write_watch(void)
{
#ifndef _WIN32
    skip("WRITE_WATCH tests cannot pass on native Linux. Skipping.\n");
#else
    D3D12_HEAP_PROPERTIES heap_properties;
    D3D12_RESOURCE_DESC resource_desc;
    struct test_context_desc desc;
    void **dirty_addresses = NULL;
    struct test_context context;
    ULONG_PTR address_count;
    ID3D12Resource *buffer;
    size_t mapping_size;
    DWORD page_size;
    char *map_ptr;
    UINT result;
    HRESULT hr;

    mapping_size = 64 * 1024;

    memset(&desc, 0, sizeof(desc));
    desc.no_render_target = true;
    desc.no_pipeline = true;
    desc.no_root_signature = true;
    if (!init_test_context(&context, &desc))
        return;

    memset(&heap_properties, 0, sizeof(heap_properties));

    resource_desc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER;
    resource_desc.Alignment = 0;
    resource_desc.Width = mapping_size;
    resource_desc.Height = 1;
    resource_desc.DepthOrArraySize = 1;
    resource_desc.MipLevels = 1;
    resource_desc.Format = DXGI_FORMAT_UNKNOWN;
    resource_desc.SampleDesc.Count = 1;
    resource_desc.SampleDesc.Quality = 0;
    resource_desc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR;

    buffer = NULL;
    heap_properties.Type = D3D12_HEAP_TYPE_DEFAULT;
    resource_desc.Flags = D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS;
    hr = ID3D12Device_CreateCommittedResource(context.device, &heap_properties,
            D3D12_HEAP_FLAG_ALLOW_WRITE_WATCH, &resource_desc, D3D12_RESOURCE_STATE_UNORDERED_ACCESS,
            NULL, &IID_ID3D12Resource, (void **)&buffer);
    ok(hr == E_INVALIDARG, "Got hr %#x, expected %#x.\n", hr, E_INVALIDARG);
    if (buffer)
        ID3D12Resource_Release(buffer);

    buffer = NULL;
    heap_properties.Type = D3D12_HEAP_TYPE_UPLOAD;
    resource_desc.Flags = D3D12_RESOURCE_FLAG_NONE;
    hr = ID3D12Device_CreateCommittedResource(context.device, &heap_properties,
            D3D12_HEAP_FLAG_ALLOW_WRITE_WATCH, &resource_desc, D3D12_RESOURCE_STATE_GENERIC_READ,
            NULL, &IID_ID3D12Resource, (void **)&buffer);
    ok(hr == S_OK, "Got hr %#x, expected %#x.\n", hr, S_OK);

    if (FAILED(hr))
    {
        skip("Failed to create write watch buffer.\n");
        goto done;
    }

    /* Do some basic write watch testing... */
    hr = ID3D12Resource_Map(buffer, 0, NULL, (void**) &map_ptr);
    ok(hr == S_OK, "Got hr %#x, expected %#x.\n", hr, S_OK);
    if (FAILED(hr))
    {
        skip("Failed to map write watch resource.\n");
        goto done;
    }

    result = ResetWriteWatch((void*) map_ptr, mapping_size);
    ok(!result, "Failed to ResetWriteWatch %#x.\n", GetLastError());
    if (result)
    {
        skip("Failed to ResetWriteWatch, skipping the rest of the WRITE_WATCH tests.\n");
        goto done;
    }

    page_size = 0x1000;
    address_count = mapping_size / (DWORD_PTR)page_size;
    dirty_addresses = malloc(sizeof(void*) * address_count);

    /* Dirty it a bit, in some pages... */
    map_ptr[0 * page_size] = 'a';
    map_ptr[1 * page_size] = 'b';
    map_ptr[5 * page_size] = 'c';
    map_ptr[9 * page_size] = 'd';

    result = GetWriteWatch(WRITE_WATCH_FLAG_RESET, (void*) map_ptr, mapping_size, dirty_addresses, &address_count, &page_size);
    ok(!result, "Failed to GetWriteWatch %#x.\n", GetLastError());
    if (result)
    {
        skip("Failed to GetWriteWatch, skipping the rest of the WRITE_WATCH tests.\n");
        goto done;
    }

    ok(address_count == 4, "Expected address_count of %p, got %p\n", 4, address_count);
    ok(page_size == 0x1000, "Expected page_size of %u, got %u\n", 0x1000, page_size);
    ok(dirty_addresses[0] == (void*)&map_ptr[0 * page_size], "Expected dirty address 0 to be %p, got %p\n",
            (void*)&map_ptr[0 * page_size], dirty_addresses[0]);
    ok(dirty_addresses[1] == (void*)&map_ptr[1 * page_size], "Expected dirty address 1 to be %p, got %p\n",
            (void*)&map_ptr[1 * page_size], dirty_addresses[1]);
    ok(dirty_addresses[2] == (void*)&map_ptr[5 * page_size], "Expected dirty address 2 to be %p, got %p\n",
            (void*)&map_ptr[5 * page_size], dirty_addresses[2]);
    ok(dirty_addresses[3] == (void*)&map_ptr[9 * page_size], "Expected dirty address 3 to be %p, got %p\n",
            (void*)&map_ptr[9 * page_size], dirty_addresses[3]);

done:
    free(dirty_addresses);

    if (buffer)
        ID3D12Resource_Release(buffer);

    destroy_test_context(&context);
#endif
}

