/*
 * Copyright 2016-2017 Józef Kucia for CodeWeavers
 *
 * 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"

PFN_D3D12_CREATE_VERSIONED_ROOT_SIGNATURE_DESERIALIZER pfn_D3D12CreateVersionedRootSignatureDeserializer;
PFN_D3D12_SERIALIZE_VERSIONED_ROOT_SIGNATURE pfn_D3D12SerializeVersionedRootSignature;
PFN_D3D12_CREATE_DEVICE pfn_D3D12CreateDevice;
PFN_D3D12_ENABLE_EXPERIMENTAL_FEATURES pfn_D3D12EnableExperimentalFeatures;
PFN_D3D12_GET_DEBUG_INTERFACE pfn_D3D12GetDebugInterface;
const char *vkd3d_test_platform = "other";
struct vkd3d_test_state_context vkd3d_test_state;

#ifdef _WIN32
RENDERDOC_API_1_0_0 *renderdoc_api;
#endif

bool compare_float(float f, float g, int ulps)
{
    int x, y;
    union
    {
        float f;
        int i;
    } u;

    u.f = f;
    x = u.i;
    u.f = g;
    y = u.i;

    if (x < 0)
        x = INT_MIN - x;
    if (y < 0)
        y = INT_MIN - y;

    if (abs(x - y) > ulps)
        return false;

    return true;
}

bool compare_vec4(const struct vec4 *v1, const struct vec4 *v2, unsigned int ulps)
{
    return compare_float(v1->x, v2->x, ulps)
            && compare_float(v1->y, v2->y, ulps)
            && compare_float(v1->z, v2->z, ulps)
            && compare_float(v1->w, v2->w, ulps);
}

bool compare_uvec4(const struct uvec4* v1, const struct uvec4 *v2)
{
    return v1->x == v2->x && v1->y == v2->y && v1->z == v2->z && v1->w == v2->w;
}

bool compare_uint8(uint8_t a, uint8_t b, unsigned int max_diff)
{
    return delta_uint8(a, b) <= max_diff;
}

bool compare_uint16(uint16_t a, uint16_t b, unsigned int max_diff)
{
    return delta_uint16(a, b) <= max_diff;
}

bool compare_uint64(uint64_t a, uint64_t b, unsigned int max_diff)
{
    return delta_uint64(a, b) <= max_diff;
}

ULONG get_refcount(void *iface)
{
    IUnknown *unk = iface;
    IUnknown_AddRef(unk);
    return IUnknown_Release(unk);
}

void check_interface_(unsigned int line, IUnknown *iface, REFIID riid, bool supported)
{
    HRESULT hr, expected_hr;
    IUnknown *unk;

    expected_hr = supported ? S_OK : E_NOINTERFACE;

    hr = IUnknown_QueryInterface(iface, riid, (void **)&unk);
    ok_(line)(hr == expected_hr, "Got hr %#x, expected %#x.\n", hr, expected_hr);
    if (SUCCEEDED(hr))
        IUnknown_Release(unk);
}

void check_heap_properties_(unsigned int line,
        const D3D12_HEAP_PROPERTIES *properties, const D3D12_HEAP_PROPERTIES *expected_properties)
{
    D3D12_HEAP_PROPERTIES expected = *expected_properties;

    if (!expected.CreationNodeMask)
        expected.CreationNodeMask = 0x1;
    if (!expected.VisibleNodeMask)
        expected.VisibleNodeMask = 0x1;

    ok_(line)(properties->Type == expected.Type,
            "Got type %#x, expected %#x.\n", properties->Type, expected.Type);
    ok_(line)(properties->CPUPageProperty == expected.CPUPageProperty,
            "Got CPU page properties %#x, expected %#x.\n",
            properties->CPUPageProperty, expected.CPUPageProperty);
    ok_(line)(properties->MemoryPoolPreference == expected.MemoryPoolPreference,
            "Got memory pool %#x, expected %#x.\n",
            properties->MemoryPoolPreference, expected.MemoryPoolPreference);
    ok_(line)(properties->CreationNodeMask == expected.CreationNodeMask,
            "Got creation node mask %#x, expected %#x.\n",
            properties->CreationNodeMask, expected.CreationNodeMask);
    ok_(line)(properties->VisibleNodeMask == expected.VisibleNodeMask,
            "Got visible node mask %#x, expected %#x.\n",
            properties->VisibleNodeMask, expected.VisibleNodeMask);
}

void check_heap_desc_(unsigned int line, const D3D12_HEAP_DESC *desc,
        const D3D12_HEAP_DESC *expected_desc)
{
    D3D12_HEAP_DESC expected = *expected_desc;

    if (!expected.Alignment)
        expected.Alignment = D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT;

    ok_(line)(desc->SizeInBytes == expected.SizeInBytes,
            "Got size %"PRIu64", expected %"PRIu64".\n",
            desc->SizeInBytes, expected.SizeInBytes);
    check_heap_properties_(line, &desc->Properties, &expected.Properties);
    ok_(line)(desc->Alignment == expected.Alignment,
            "Got alignment %"PRIu64", expected %"PRIu64".\n",
            desc->Alignment, expected.Alignment);
    ok_(line)(desc->Flags == expected.Flags,
            "Got flags %#x, expected %#x.\n", desc->Flags, expected.Flags);
}

void check_alignment_(unsigned int line, uint64_t size, uint64_t alignment)
{
    uint64_t aligned_size = align(size, alignment);
    ok_(line)(aligned_size == size, "Got unaligned size %"PRIu64", expected %"PRIu64".\n",
            size, aligned_size);
}

void uav_barrier(ID3D12GraphicsCommandList *list, ID3D12Resource *resource)
{
    D3D12_RESOURCE_BARRIER barrier;

    barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_UAV;
    barrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE;
    barrier.UAV.pResource = resource;

    ID3D12GraphicsCommandList_ResourceBarrier(list, 1, &barrier);
}

void copy_sub_resource_data(const D3D12_MEMCPY_DEST *dst, const D3D12_SUBRESOURCE_DATA *src,
        unsigned int row_count, unsigned int slice_count, size_t row_size)
{
    const BYTE *src_slice_ptr;
    BYTE *dst_slice_ptr;
    unsigned int z, y;

    for (z = 0; z < slice_count; ++z)
    {
        dst_slice_ptr = (BYTE *)dst->pData + z * dst->SlicePitch;
        src_slice_ptr = (const BYTE*)src->pData + z * src->SlicePitch;
        for (y = 0; y < row_count; ++y)
            memcpy(dst_slice_ptr + y * dst->RowPitch, src_slice_ptr + y * src->RowPitch, row_size);
    }
}

void upload_buffer_data_(unsigned int line, ID3D12Resource *buffer, size_t offset,
        size_t size, const void *data, ID3D12CommandQueue *queue, ID3D12GraphicsCommandList *command_list)
{
    ID3D12Resource *upload_buffer;
    ID3D12Device *device;
    HRESULT hr;

    hr = ID3D12Resource_GetDevice(buffer, &IID_ID3D12Device, (void **)&device);
    ok_(line)(SUCCEEDED(hr), "Failed to get device, hr %#x.\n", hr);

    upload_buffer = create_upload_buffer_(line, device, size, data);

    ID3D12GraphicsCommandList_CopyBufferRegion(command_list, buffer, offset,
            upload_buffer, 0, size);

    hr = ID3D12GraphicsCommandList_Close(command_list);
    ok_(line)(SUCCEEDED(hr), "Failed to close command list, hr %#x.\n", hr);
    exec_command_list(queue, command_list);
    wait_queue_idle(device, queue);

    ID3D12Resource_Release(upload_buffer);
    ID3D12Device_Release(device);
}

void upload_texture_data_(unsigned int line, ID3D12Resource *texture,
        const D3D12_SUBRESOURCE_DATA *data, unsigned int sub_resource_count,
        ID3D12CommandQueue *queue, ID3D12GraphicsCommandList *command_list)
{
    D3D12_TEXTURE_COPY_LOCATION dst_location, src_location;
    D3D12_PLACED_SUBRESOURCE_FOOTPRINT *layouts;
    D3D12_RESOURCE_DESC resource_desc;
    UINT64 *row_sizes, required_size;
    ID3D12Resource *upload_buffer;
    D3D12_MEMCPY_DEST dst_data;
    ID3D12Device *device;
    UINT *row_counts;
    unsigned int i;
    HRESULT hr;
    void *ptr;

    layouts = calloc(sub_resource_count, sizeof(*layouts));
    ok(layouts, "Failed to allocate memory.\n");
    row_counts = calloc(sub_resource_count, sizeof(*row_counts));
    ok(row_counts, "Failed to allocate memory.\n");
    row_sizes = calloc(sub_resource_count, sizeof(*row_sizes));
    ok(row_sizes, "Failed to allocate memory.\n");

    resource_desc = ID3D12Resource_GetDesc(texture);
    hr = ID3D12Resource_GetDevice(texture, &IID_ID3D12Device, (void **)&device);
    ok_(line)(SUCCEEDED(hr), "Failed to get device, hr %#x.\n", hr);

    ID3D12Device_GetCopyableFootprints(device, &resource_desc, 0, sub_resource_count,
            0, layouts, row_counts, row_sizes, &required_size);

    upload_buffer = create_upload_buffer_(line, device, required_size, NULL);

    hr = ID3D12Resource_Map(upload_buffer, 0, NULL, (void **)&ptr);
    ok_(line)(SUCCEEDED(hr), "Failed to map upload buffer, hr %#x.\n", hr);
    for (i = 0; i < sub_resource_count; ++i)
    {
        dst_data.pData = (BYTE *)ptr + layouts[i].Offset;
        dst_data.RowPitch = layouts[i].Footprint.RowPitch;
        dst_data.SlicePitch = layouts[i].Footprint.RowPitch * row_counts[i];
        copy_sub_resource_data(&dst_data, &data[i],
                row_counts[i], layouts[i].Footprint.Depth, row_sizes[i]);
    }
    ID3D12Resource_Unmap(upload_buffer, 0, NULL);

    for (i = 0; i < sub_resource_count; ++i)
    {
        dst_location.pResource = texture;
        dst_location.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX;
        dst_location.SubresourceIndex = i;

        src_location.pResource = upload_buffer;
        src_location.Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT;
        src_location.PlacedFootprint = layouts[i];

        ID3D12GraphicsCommandList_CopyTextureRegion(command_list,
                &dst_location, 0, 0, 0, &src_location, NULL);
    }

    hr = ID3D12GraphicsCommandList_Close(command_list);
    ok_(line)(SUCCEEDED(hr), "Failed to close command list, hr %#x.\n", hr);

    exec_command_list(queue, command_list);
    wait_queue_idle(device, queue);

    ID3D12Resource_Release(upload_buffer);
    ID3D12Device_Release(device);

    free(layouts);
    free(row_counts);
    free(row_sizes);
}

void init_readback(struct resource_readback *rb, ID3D12Resource *buffer,
        uint64_t buffer_size, uint64_t width, uint64_t height, unsigned int depth, uint64_t row_pitch)
{
    D3D12_RANGE read_range;
    HRESULT hr;

    rb->width = width;
    rb->height = height;
    rb->depth = depth;
    rb->resource = buffer;
    rb->row_pitch = row_pitch;
    rb->data = NULL;

    ID3D12Resource_AddRef(rb->resource);

    read_range.Begin = 0;
    read_range.End = buffer_size;
    hr = ID3D12Resource_Map(rb->resource, 0, &read_range, &rb->data);
    ok(hr == S_OK, "Failed to map readback buffer, hr %#x.\n", hr);
}

void get_buffer_readback_with_command_list(ID3D12Resource *buffer, DXGI_FORMAT format,
        struct resource_readback *rb, ID3D12CommandQueue *queue, ID3D12GraphicsCommandList *command_list)
{
    D3D12_HEAP_PROPERTIES heap_properties;
    D3D12_RESOURCE_DESC resource_desc;
    ID3D12Resource *rb_buffer;
    D3D12_RANGE read_range;
    ID3D12Device *device;
    HRESULT hr;

    hr = ID3D12Resource_GetDevice(buffer, &IID_ID3D12Device, (void **)&device);
    ok(SUCCEEDED(hr), "Failed to get device, hr %#x.\n", hr);

    resource_desc = ID3D12Resource_GetDesc(buffer);
    assert(resource_desc.Dimension == D3D12_RESOURCE_DIMENSION_BUFFER);
    resource_desc.Flags = D3D12_RESOURCE_FLAG_DENY_SHADER_RESOURCE;

    hr = ID3D12Resource_GetHeapProperties(buffer, &heap_properties, NULL);
    if (SUCCEEDED(hr) && heap_properties.Type == D3D12_HEAP_TYPE_READBACK)
    {
        rb_buffer = buffer;
        ID3D12Resource_AddRef(rb_buffer);
    }
    else
    {
        rb_buffer = create_readback_buffer(device, resource_desc.Width);
        ID3D12GraphicsCommandList_CopyBufferRegion(command_list, rb_buffer, 0,
                buffer, 0, resource_desc.Width);
    }

    hr = ID3D12GraphicsCommandList_Close(command_list);
    ok(SUCCEEDED(hr), "Failed to close command list, hr %#x.\n", hr);

    exec_command_list(queue, command_list);
    wait_queue_idle(device, queue);
    ID3D12Device_Release(device);

    rb->width = resource_desc.Width / format_size(format);
    rb->height = 1;
    rb->depth = 1;
    rb->resource = rb_buffer;
    rb->row_pitch = resource_desc.Width;
    rb->data = NULL;

    read_range.Begin = 0;
    read_range.End = resource_desc.Width;
    hr = ID3D12Resource_Map(rb_buffer, 0, &read_range, &rb->data);
    ok(SUCCEEDED(hr), "Failed to map readback buffer, hr %#x.\n", hr);
}

uint8_t get_readback_uint8(struct resource_readback *rb, unsigned int x, unsigned int y)
{
    return *(uint8_t *)get_readback_data(rb, x, y, 0, sizeof(uint8_t));
}

uint16_t get_readback_uint16(struct resource_readback *rb, unsigned int x, unsigned int y)
{
    return *(uint16_t *)get_readback_data(rb, x, y, 0, sizeof(uint16_t));
}

uint64_t get_readback_uint64(struct resource_readback *rb, unsigned int x, unsigned int y)
{
    return *(uint64_t *)get_readback_data(rb, x, y, 0, sizeof(uint64_t));
}

float get_readback_float(struct resource_readback *rb, unsigned int x, unsigned int y)
{
    return *(float *)get_readback_data(rb, x, y, 0, sizeof(float));
}

const struct vec4 *get_readback_vec4(struct resource_readback *rb, unsigned int x, unsigned int y)
{
    return get_readback_data(rb, x, y, 0, sizeof(struct vec4));
}

const struct uvec4 *get_readback_uvec4(struct resource_readback *rb, unsigned int x, unsigned int y)
{
    return get_readback_data(rb, x, y, 0, sizeof(struct uvec4));
}

void check_readback_data_float_(unsigned int line, struct resource_readback *rb,
        const RECT *rect, float expected, unsigned int max_diff)
{
    RECT r = {0, 0, rb->width, rb->height};
    int x = 0, y;
    bool all_match = true;
    float got = 0;

    if (rect)
        r = *rect;

    for (y = r.top; y < r.bottom; ++y)
    {
        for (x = r.left; x < r.right; ++x)
        {
            got = get_readback_float(rb, x, y);
            if (!compare_float(got, expected, max_diff))
            {
                all_match = false;
                break;
            }
        }
        if (!all_match)
            break;
    }
    ok_(line)(all_match, "Got %.8e, expected %.8e at (%u, %u).\n", got, expected, x, y);
}

void check_sub_resource_float_(unsigned int line, ID3D12Resource *texture,
        unsigned int sub_resource_idx, ID3D12CommandQueue *queue, ID3D12GraphicsCommandList *command_list,
        float expected, unsigned int max_diff)
{
    struct resource_readback rb;

    get_texture_readback_with_command_list(texture, sub_resource_idx, &rb, queue, command_list);
    check_readback_data_float_(line, &rb, NULL, expected, max_diff);
    release_resource_readback(&rb);
}

void check_readback_data_uint8_(unsigned int line, struct resource_readback *rb,
        const RECT *rect, uint8_t expected, unsigned int max_diff)
{
    RECT r = {0, 0, rb->width, rb->height};
    int x = 0, y;
    bool all_match = true;
    uint8_t got = 0;

    if (rect)
        r = *rect;

    for (y = r.top; y < r.bottom; ++y)
    {
        for (x = r.left; x < r.right; ++x)
        {
            got = get_readback_uint8(rb, x, y);
            if (!compare_uint8(got, expected, max_diff))
            {
                all_match = false;
                break;
            }
        }
        if (!all_match)
            break;
    }
    ok_(line)(all_match, "Got 0x%02x, expected 0x%02x at (%u, %u).\n", got, expected, x, y);
}

void check_sub_resource_uint8_(unsigned int line, ID3D12Resource *texture,
        unsigned int sub_resource_idx, ID3D12CommandQueue *queue, ID3D12GraphicsCommandList *command_list,
        uint8_t expected, unsigned int max_diff)
{
    struct resource_readback rb;

    get_texture_readback_with_command_list(texture, sub_resource_idx, &rb, queue, command_list);
    check_readback_data_uint8_(line, &rb, NULL, expected, max_diff);
    release_resource_readback(&rb);
}

void check_readback_data_uint16_(unsigned int line, struct resource_readback *rb,
        const RECT *rect, uint16_t expected, unsigned int max_diff)
{
    RECT r = {0, 0, rb->width, rb->height};
    int x = 0, y;
    bool all_match = true;
    uint16_t got = 0;

    if (rect)
        r = *rect;

    for (y = r.top; y < r.bottom; ++y)
    {
        for (x = r.left; x < r.right; ++x)
        {
            got = get_readback_uint16(rb, x, y);
            if (!compare_uint16(got, expected, max_diff))
            {
                all_match = false;
                break;
            }
        }
        if (!all_match)
            break;
    }
    ok_(line)(all_match, "Got 0x%04x, expected 0x%04x at (%u, %u).\n", got, expected, x, y);
}

void check_sub_resource_uint16_(unsigned int line, ID3D12Resource *texture,
        unsigned int sub_resource_idx, ID3D12CommandQueue *queue, ID3D12GraphicsCommandList *command_list,
        uint16_t expected, unsigned int max_diff)
{
    struct resource_readback rb;

    get_texture_readback_with_command_list(texture, sub_resource_idx, &rb, queue, command_list);
    check_readback_data_uint16_(line, &rb, NULL, expected, max_diff);
    release_resource_readback(&rb);
}

void check_readback_data_uint64_(unsigned int line, struct resource_readback *rb,
        const RECT *rect, uint64_t expected, unsigned int max_diff)
{
    RECT r = {0, 0, rb->width, rb->height};
    int x = 0, y;
    bool all_match = true;
    uint64_t got = 0;

    if (rect)
        r = *rect;

    for (y = r.top; y < r.bottom; ++y)
    {
        for (x = r.left; x < r.right; ++x)
        {
            got = get_readback_uint64(rb, x, y);
            if (!compare_uint64(got, expected, max_diff))
            {
                all_match = false;
                break;
            }
        }
        if (!all_match)
            break;
    }
    ok_(line)(all_match, "Got %#"PRIx64", expected %#"PRIx64" at (%u, %u).\n", got, expected, x, y);
}

void check_sub_resource_uint64_(unsigned int line, ID3D12Resource *texture,
        unsigned int sub_resource_idx, ID3D12CommandQueue *queue, ID3D12GraphicsCommandList *command_list,
        uint64_t expected, unsigned int max_diff)
{
    struct resource_readback rb;

    get_texture_readback_with_command_list(texture, sub_resource_idx, &rb, queue, command_list);
    check_readback_data_uint64_(line, &rb, NULL, expected, max_diff);
    release_resource_readback(&rb);
}

void check_sub_resource_vec4_(unsigned int line, ID3D12Resource *texture,
        unsigned int sub_resource_idx, ID3D12CommandQueue *queue, ID3D12GraphicsCommandList *command_list,
        const struct vec4 *expected, unsigned int max_diff)
{
    struct resource_readback rb;
    unsigned int x = 0, y;
    bool all_match = true;
    struct vec4 got = {0};

    get_texture_readback_with_command_list(texture, sub_resource_idx, &rb, queue, command_list);
    for (y = 0; y < rb.height; ++y)
    {
        for (x = 0; x < rb.width; ++x)
        {
            got = *get_readback_vec4(&rb, x, y);
            if (!compare_vec4(&got, expected, max_diff))
            {
                all_match = false;
                break;
            }
        }
        if (!all_match)
            break;
    }
    release_resource_readback(&rb);

    ok_(line)(all_match, "Got {%.8e, %.8e, %.8e, %.8e}, expected {%.8e, %.8e, %.8e, %.8e} at (%u, %u).\n",
            got.x, got.y, got.z, got.w, expected->x, expected->y, expected->z, expected->w, x, y);
}

void check_sub_resource_uvec4_(unsigned int line, ID3D12Resource *texture,
        unsigned int sub_resource_idx, ID3D12CommandQueue *queue, ID3D12GraphicsCommandList *command_list,
        const struct uvec4 *expected_value)
{
    struct resource_readback rb;
    struct uvec4 value = {0};
    unsigned int x = 0, y;
    bool all_match = true;

    get_texture_readback_with_command_list(texture, sub_resource_idx, &rb, queue, command_list);
    for (y = 0; y < rb.height; ++y)
    {
        for (x = 0; x < rb.width; ++x)
        {
            value = *get_readback_uvec4(&rb, x, y);
            if (!compare_uvec4(&value, expected_value))
            {
                all_match = false;
                break;
            }
        }
        if (!all_match)
            break;
    }
    release_resource_readback(&rb);

    ok_(line)(all_match,
            "Got {0x%08x, 0x%08x, 0x%08x, 0x%08x}, expected {0x%08x, 0x%08x, 0x%08x, 0x%08x} at (%u, %u).\n",
            value.x, value.y, value.z, value.w,
            expected_value->x, expected_value->y, expected_value->z, expected_value->w, x, y);
}

bool broken_on_warp(bool condition)
{
    return broken(use_warp_device && condition);
}

bool is_min_max_filtering_supported(ID3D12Device *device)
{
    D3D12_FEATURE_DATA_D3D12_OPTIONS options;
    HRESULT hr;

    if (FAILED(hr = ID3D12Device_CheckFeatureSupport(device,
            D3D12_FEATURE_D3D12_OPTIONS, &options, sizeof(options))))
    {
        trace("Failed to check feature support, hr %#x.\n", hr);
        return false;
    }

    /* D3D12 validation layer says tiled resource tier 2+ support implies min/max filtering support. */
    return options.TiledResourcesTier >= D3D12_TILED_RESOURCES_TIER_2;
}

D3D12_TILED_RESOURCES_TIER get_tiled_resources_tier(ID3D12Device *device)
{
    D3D12_FEATURE_DATA_D3D12_OPTIONS options;
    HRESULT hr;

    if (FAILED(hr = ID3D12Device_CheckFeatureSupport(device,
            D3D12_FEATURE_D3D12_OPTIONS, &options, sizeof(options))))
    {
        trace("Failed to check feature support, hr %#x.\n", hr);
        return D3D12_TILED_RESOURCES_TIER_NOT_SUPPORTED;
    }

    return options.TiledResourcesTier;
}

bool is_standard_swizzle_64kb_supported(ID3D12Device *device)
{
    D3D12_FEATURE_DATA_D3D12_OPTIONS options;
    HRESULT hr;

    if (FAILED(hr = ID3D12Device_CheckFeatureSupport(device,
            D3D12_FEATURE_D3D12_OPTIONS, &options, sizeof(options))))
    {
        trace("Failed to check feature support, hr %#x.\n", hr);
        return false;
    }

    return options.StandardSwizzle64KBSupported;
}

bool is_memory_pool_L1_supported(ID3D12Device *device)
{
    D3D12_FEATURE_DATA_ARCHITECTURE architecture;
    HRESULT hr;

    memset(&architecture, 0, sizeof(architecture));
    if (FAILED(hr = ID3D12Device_CheckFeatureSupport(device, D3D12_FEATURE_ARCHITECTURE,
            &architecture, sizeof(architecture))))
    {
        trace("Failed to check feature support, hr %#x.\n", hr);
        return false;
    }

    return !architecture.UMA;
}

bool is_vrs_tier1_supported(ID3D12Device *device, bool *additional_shading_rates)
{
    D3D12_FEATURE_DATA_D3D12_OPTIONS6 options;
    HRESULT hr;

    if (additional_shading_rates)
        *additional_shading_rates = false;

    if (FAILED(hr = ID3D12Device_CheckFeatureSupport(device,
            D3D12_FEATURE_D3D12_OPTIONS6, &options, sizeof(options))))
    {
        trace("Failed to check feature support, hr %#x.\n", hr);
        return false;
    }

    if (additional_shading_rates)
        *additional_shading_rates = options.AdditionalShadingRatesSupported;

    return options.VariableShadingRateTier >= D3D12_VARIABLE_SHADING_RATE_TIER_1;
}

bool is_vrs_tier2_supported(ID3D12Device *device)
{
    D3D12_FEATURE_DATA_D3D12_OPTIONS6 options;
    HRESULT hr;

    if (FAILED(hr = ID3D12Device_CheckFeatureSupport(device,
            D3D12_FEATURE_D3D12_OPTIONS6, &options, sizeof(options))))
    {
        trace("Failed to check feature support, hr %#x.\n", hr);
        return false;
    }
    return options.VariableShadingRateTier >= D3D12_VARIABLE_SHADING_RATE_TIER_2;
}

ID3D12RootSignature *create_cb_root_signature_(unsigned int line,
        ID3D12Device *device, unsigned int reg_idx, D3D12_SHADER_VISIBILITY shader_visibility,
        D3D12_ROOT_SIGNATURE_FLAGS flags)
{
    D3D12_ROOT_SIGNATURE_DESC root_signature_desc;
    ID3D12RootSignature *root_signature = NULL;
    D3D12_ROOT_PARAMETER root_parameter;
    HRESULT hr;

    root_parameter.ParameterType = D3D12_ROOT_PARAMETER_TYPE_CBV;
    root_parameter.Descriptor.ShaderRegister = reg_idx;
    root_parameter.Descriptor.RegisterSpace = 0;
    root_parameter.ShaderVisibility = shader_visibility;

    memset(&root_signature_desc, 0, sizeof(root_signature_desc));
    root_signature_desc.NumParameters = 1;
    root_signature_desc.pParameters = &root_parameter;
    root_signature_desc.Flags = flags;
    hr = create_root_signature(device, &root_signature_desc, &root_signature);
    ok_(line)(SUCCEEDED(hr), "Failed to create root signature, hr %#x.\n", hr);

    return root_signature;
}

ID3D12RootSignature *create_32bit_constants_root_signature_(unsigned int line,
        ID3D12Device *device, unsigned int reg_idx, unsigned int element_count,
        D3D12_SHADER_VISIBILITY shader_visibility, D3D12_ROOT_SIGNATURE_FLAGS flags)
{
    D3D12_ROOT_SIGNATURE_DESC root_signature_desc;
    ID3D12RootSignature *root_signature = NULL;
    D3D12_ROOT_PARAMETER root_parameter;
    HRESULT hr;

    root_parameter.ParameterType = D3D12_ROOT_PARAMETER_TYPE_32BIT_CONSTANTS;
    root_parameter.Constants.ShaderRegister = reg_idx;
    root_parameter.Constants.RegisterSpace = 0;
    root_parameter.Constants.Num32BitValues = element_count;
    root_parameter.ShaderVisibility = shader_visibility;

    memset(&root_signature_desc, 0, sizeof(root_signature_desc));
    root_signature_desc.NumParameters = 1;
    root_signature_desc.pParameters = &root_parameter;
    root_signature_desc.Flags = flags;
    hr = create_root_signature(device, &root_signature_desc, &root_signature);
    ok_(line)(SUCCEEDED(hr), "Failed to create root signature, hr %#x.\n", hr);

    return root_signature;
}

ID3D12RootSignature *create_texture_root_signature_(unsigned int line,
        ID3D12Device *device, D3D12_SHADER_VISIBILITY shader_visibility,
        unsigned int constant_count, D3D12_ROOT_SIGNATURE_FLAGS flags,
        const D3D12_STATIC_SAMPLER_DESC *sampler_desc)
{
    D3D12_ROOT_SIGNATURE_DESC root_signature_desc;
    ID3D12RootSignature *root_signature = NULL;
    D3D12_STATIC_SAMPLER_DESC static_sampler;
    D3D12_DESCRIPTOR_RANGE descriptor_range;
    D3D12_ROOT_PARAMETER root_parameters[2];
    HRESULT hr;

    if (sampler_desc)
    {
        static_sampler = *sampler_desc;
    }
    else
    {
        memset(&static_sampler, 0, sizeof(static_sampler));
        static_sampler.Filter = D3D12_FILTER_MIN_MAG_MIP_POINT;
        static_sampler.AddressU = D3D12_TEXTURE_ADDRESS_MODE_CLAMP;
        static_sampler.AddressV = D3D12_TEXTURE_ADDRESS_MODE_CLAMP;
        static_sampler.AddressW = D3D12_TEXTURE_ADDRESS_MODE_CLAMP;
        static_sampler.MaxLOD = D3D12_FLOAT32_MAX;
        static_sampler.ShaderRegister = 0;
        static_sampler.RegisterSpace = 0;
        static_sampler.ShaderVisibility = shader_visibility;
    }

    descriptor_range.RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_SRV;
    descriptor_range.NumDescriptors = 1;
    descriptor_range.BaseShaderRegister = 0;
    descriptor_range.RegisterSpace = 0;
    descriptor_range.OffsetInDescriptorsFromTableStart = 0;
    root_parameters[0].ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE;
    root_parameters[0].DescriptorTable.NumDescriptorRanges = 1;
    root_parameters[0].DescriptorTable.pDescriptorRanges = &descriptor_range;
    root_parameters[0].ShaderVisibility = shader_visibility;

    root_parameters[1].ParameterType = D3D12_ROOT_PARAMETER_TYPE_32BIT_CONSTANTS;
    root_parameters[1].Constants.ShaderRegister = 0;
    root_parameters[1].Constants.RegisterSpace = 0;
    root_parameters[1].Constants.Num32BitValues = constant_count;
    root_parameters[1].ShaderVisibility = shader_visibility;

    memset(&root_signature_desc, 0, sizeof(root_signature_desc));
    root_signature_desc.NumParameters = constant_count ? 2 : 1;
    root_signature_desc.pParameters = root_parameters;
    root_signature_desc.NumStaticSamplers = 1;
    root_signature_desc.pStaticSamplers = &static_sampler;
    root_signature_desc.Flags = flags;

    hr = create_root_signature(device, &root_signature_desc, &root_signature);
    ok_(line)(SUCCEEDED(hr), "Failed to create root signature, hr %#x.\n", hr);

    return root_signature;
}

ID3D12PipelineState *create_compute_pipeline_state_(unsigned int line, ID3D12Device *device,
        ID3D12RootSignature *root_signature, const D3D12_SHADER_BYTECODE cs)
{
    D3D12_COMPUTE_PIPELINE_STATE_DESC pipeline_state_desc;
    ID3D12PipelineState *pipeline_state = NULL;
    HRESULT hr;

    memset(&pipeline_state_desc, 0, sizeof(pipeline_state_desc));
    pipeline_state_desc.pRootSignature = root_signature;
    pipeline_state_desc.CS = cs;
    pipeline_state_desc.NodeMask = 0;
    pipeline_state_desc.Flags = D3D12_PIPELINE_STATE_FLAG_NONE;
    hr = ID3D12Device_CreateComputePipelineState(device, &pipeline_state_desc,
            &IID_ID3D12PipelineState, (void **)&pipeline_state);
    ok_(line)(SUCCEEDED(hr), "Failed to create compute pipeline state, hr %#x.\n", hr);
    return pipeline_state;
}

ID3D12CommandSignature *create_command_signature_(unsigned int line,
        ID3D12Device *device, D3D12_INDIRECT_ARGUMENT_TYPE argument_type)
{
    ID3D12CommandSignature *command_signature = NULL;
    D3D12_COMMAND_SIGNATURE_DESC signature_desc;
    D3D12_INDIRECT_ARGUMENT_DESC argument_desc;
    HRESULT hr;

    argument_desc.Type = argument_type;

    switch (argument_type)
    {
        case D3D12_INDIRECT_ARGUMENT_TYPE_DRAW:
            signature_desc.ByteStride = sizeof(D3D12_DRAW_ARGUMENTS);
            break;
        case D3D12_INDIRECT_ARGUMENT_TYPE_DRAW_INDEXED:
            signature_desc.ByteStride = sizeof(D3D12_DRAW_INDEXED_ARGUMENTS);
            break;
        case D3D12_INDIRECT_ARGUMENT_TYPE_DISPATCH:
            signature_desc.ByteStride = sizeof(D3D12_DISPATCH_ARGUMENTS);
            break;
        case D3D12_INDIRECT_ARGUMENT_TYPE_DISPATCH_RAYS:
            signature_desc.ByteStride = sizeof(D3D12_DISPATCH_RAYS_DESC);
            break;
        default:
            return NULL;
    }

    signature_desc.NumArgumentDescs = 1;
    signature_desc.pArgumentDescs = &argument_desc;
    signature_desc.NodeMask = 0;
    hr = ID3D12Device_CreateCommandSignature(device, &signature_desc,
            NULL, &IID_ID3D12CommandSignature, (void **)&command_signature);
    ok_(line)(hr == S_OK, "Failed to create command signature, hr %#x.\n", hr);

    return command_signature;
}

bool init_compute_test_context_(unsigned int line, struct test_context *context)
{
    D3D12_COMMAND_LIST_TYPE command_list_type = D3D12_COMMAND_LIST_TYPE_COMPUTE;
    ID3D12Device *device;
    HRESULT hr;

    memset(context, 0, sizeof(*context));

    if (!(context->device = create_device()))
    {
        skip_(line)("Failed to create device.\n");
        return false;
    }
    device = context->device;

#ifdef _WIN32
    begin_renderdoc_capturing(device);
    /* Workaround RenderDoc bug. It expects a DIRECT command queue to exist. */
    if (renderdoc_api)
        command_list_type = D3D12_COMMAND_LIST_TYPE_DIRECT;
#endif

    context->queue = create_command_queue_(line, device,
            command_list_type, D3D12_COMMAND_QUEUE_PRIORITY_NORMAL);

    hr = ID3D12Device_CreateCommandAllocator(device, command_list_type,
            &IID_ID3D12CommandAllocator, (void **)&context->allocator);
    ok_(line)(hr == S_OK, "Failed to create command allocator, hr %#x.\n", hr);

    hr = ID3D12Device_CreateCommandList(device, 0, command_list_type,
            context->allocator, NULL, &IID_ID3D12GraphicsCommandList, (void **)&context->list);
    ok_(line)(hr == S_OK, "Failed to create command list, hr %#x.\n", hr);

    return true;
}

bool context_supports_dxil_(unsigned int line, struct test_context *context)
{
    D3D12_FEATURE_DATA_SHADER_MODEL model;
    HRESULT hr;
    model.HighestShaderModel = D3D_SHADER_MODEL_6_0;
    hr = ID3D12Device_CheckFeatureSupport(context->device, D3D12_FEATURE_SHADER_MODEL, &model, sizeof(model));
    ok_(line)(hr == S_OK, "Failed to query shader model support, hr %#x.\n", hr);

    if (hr != S_OK)
        return false;

    if (model.HighestShaderModel < D3D_SHADER_MODEL_6_0)
    {
        skip_(line)("Device does not support shader model 6.0, skipping DXIL tests.\n");
        return false;
    }
    else
        return true;
}

void init_depth_stencil_(unsigned int line, struct depth_stencil_resource *ds,
        ID3D12Device *device, unsigned int width, unsigned int height, unsigned int array_size, unsigned int level_count,
        DXGI_FORMAT format, DXGI_FORMAT view_format, const D3D12_CLEAR_VALUE *clear_value)
{
    D3D12_DEPTH_STENCIL_VIEW_DESC dsv_desc, *view_desc;
    D3D12_HEAP_PROPERTIES heap_properties;
    D3D12_RESOURCE_DESC resource_desc;
    HRESULT hr;

    memset(ds, 0, sizeof(*ds));

    ds->heap = create_cpu_descriptor_heap(device, D3D12_DESCRIPTOR_HEAP_TYPE_DSV, 1);

    memset(&heap_properties, 0, sizeof(heap_properties));
    heap_properties.Type = D3D12_HEAP_TYPE_DEFAULT;
    resource_desc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
    resource_desc.Alignment = 0;
    resource_desc.Width = width;
    resource_desc.Height = height;
    resource_desc.DepthOrArraySize = array_size;
    resource_desc.MipLevels = level_count;
    resource_desc.Format = format;
    resource_desc.SampleDesc.Count = 1;
    resource_desc.SampleDesc.Quality = 0;
    resource_desc.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN;
    resource_desc.Flags = D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL;
    hr = ID3D12Device_CreateCommittedResource(device, &heap_properties, D3D12_HEAP_FLAG_NONE,
            &resource_desc, D3D12_RESOURCE_STATE_DEPTH_WRITE, clear_value,
            &IID_ID3D12Resource, (void **)&ds->texture);
    ok_(line)(SUCCEEDED(hr), "Failed to create texture, hr %#x.\n", hr);

    view_desc = NULL;
    if (view_format)
    {
        memset(&dsv_desc, 0, sizeof(dsv_desc));
        dsv_desc.Format = view_format;
        dsv_desc.ViewDimension = D3D12_DSV_DIMENSION_TEXTURE2D;
        view_desc = &dsv_desc;
    }
    ds->dsv_handle = ID3D12DescriptorHeap_GetCPUDescriptorHandleForHeapStart(ds->heap);
    ID3D12Device_CreateDepthStencilView(device, ds->texture, view_desc, ds->dsv_handle);
}

void destroy_depth_stencil_(unsigned int line, struct depth_stencil_resource *ds)
{
    ID3D12DescriptorHeap_Release(ds->heap);
    ID3D12Resource_Release(ds->texture);
}
