/*
 * 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_create_device(void)
{
    ID3D12Device *device;
    ULONG refcount;
    HRESULT hr;

    if (!(device = create_device()))
    {
        skip("Failed to create device.\n");
        return;
    }

    check_interface(device, &IID_ID3D12Object, true);
    check_interface(device, &IID_ID3D12DeviceChild, false);
    check_interface(device, &IID_ID3D12Pageable, false);
    check_interface(device, &IID_ID3D12Device, true);

    refcount = ID3D12Device_Release(device);
    ok(!refcount, "ID3D12Device has %u references left.\n", (unsigned int)refcount);

    hr = D3D12CreateDevice(NULL, D3D_FEATURE_LEVEL_11_0, &IID_ID3D12Device, (void **)&device);
    ok(hr == S_OK, "Failed to create device, hr %#x.\n", hr);
    ID3D12Device_Release(device);

    hr = D3D12CreateDevice(NULL, D3D_FEATURE_LEVEL_11_0, &IID_ID3D12Device, NULL);
    ok(hr == S_FALSE, "Got unexpected hr %#x.\n", hr);
    hr = D3D12CreateDevice(NULL, D3D_FEATURE_LEVEL_11_0, NULL, NULL);
    ok(hr == S_FALSE, "Got unexpected hr %#x.\n", hr);
    hr = D3D12CreateDevice(NULL, D3D_FEATURE_LEVEL_11_0, &IID_ID3D12DeviceChild, NULL);
    ok(hr == S_FALSE, "Got unexpected hr %#x.\n", hr);

    hr = D3D12CreateDevice(NULL, D3D_FEATURE_LEVEL_9_1, &IID_ID3D12Device, (void **)&device);
    ok(hr == E_INVALIDARG, "Got unexpected hr %#x.\n", hr);
    hr = D3D12CreateDevice(NULL, D3D_FEATURE_LEVEL_9_2, &IID_ID3D12Device, (void **)&device);
    ok(hr == E_INVALIDARG, "Got unexpected hr %#x.\n", hr);
    hr = D3D12CreateDevice(NULL, D3D_FEATURE_LEVEL_9_3, &IID_ID3D12Device, (void **)&device);
    ok(hr == E_INVALIDARG, "Got unexpected hr %#x.\n", hr);
    hr = D3D12CreateDevice(NULL, D3D_FEATURE_LEVEL_10_0, &IID_ID3D12Device, (void **)&device);
    ok(hr == E_INVALIDARG, "Got unexpected hr %#x.\n", hr);
    hr = D3D12CreateDevice(NULL, D3D_FEATURE_LEVEL_10_1, &IID_ID3D12Device, (void **)&device);
    ok(hr == E_INVALIDARG, "Got unexpected hr %#x.\n", hr);

    hr = D3D12CreateDevice(NULL, 0, &IID_ID3D12Device, (void **)&device);
    ok(hr == E_INVALIDARG, "Got unexpected hr %#x.\n", hr);
    hr = D3D12CreateDevice(NULL, ~0u, &IID_ID3D12Device, (void **)&device);
    ok(hr == E_INVALIDARG, "Got unexpected hr %#x.\n", hr);
}

void test_node_count(void)
{
    ID3D12Device *device;
    UINT node_count;
    ULONG refcount;

    if (!(device = create_device()))
    {
        skip("Failed to create device.\n");
        return;
    }

    node_count = ID3D12Device_GetNodeCount(device);
    trace("Node count: %u.\n", node_count);
    ok(1 <= node_count && node_count <= 32, "Got unexpected node count %u.\n", node_count);

    refcount = ID3D12Device_Release(device);
    ok(!refcount, "ID3D12Device has %u references left.\n", (unsigned int)refcount);
}

void test_check_feature_support(void)
{
    D3D12_FEATURE_DATA_GPU_VIRTUAL_ADDRESS_SUPPORT gpu_virtual_address;
    D3D12_FEATURE_DATA_FEATURE_LEVELS feature_levels;
    D3D12_FEATURE_DATA_ROOT_SIGNATURE root_signature;
    D3D_FEATURE_LEVEL max_supported_feature_level;
    D3D12_FEATURE_DATA_ARCHITECTURE architecture;
    D3D12_FEATURE_DATA_FORMAT_INFO format_info;
    unsigned int expected_plane_count;
    ID3D12Device *device;
    DXGI_FORMAT format;
    ULONG refcount;
    bool is_todo;
    HRESULT hr;

    static const D3D_FEATURE_LEVEL all_feature_levels[] =
    {
        D3D_FEATURE_LEVEL_12_2,
        D3D_FEATURE_LEVEL_12_1,
        D3D_FEATURE_LEVEL_12_0,
        D3D_FEATURE_LEVEL_11_1,
        D3D_FEATURE_LEVEL_11_0,
        D3D_FEATURE_LEVEL_10_1,
        D3D_FEATURE_LEVEL_10_0,
        D3D_FEATURE_LEVEL_9_3,
        D3D_FEATURE_LEVEL_9_2,
        D3D_FEATURE_LEVEL_9_1,
    };
    static const D3D_FEATURE_LEVEL d3d12_feature_levels[] =
    {
        D3D_FEATURE_LEVEL_12_2,
        D3D_FEATURE_LEVEL_12_1,
        D3D_FEATURE_LEVEL_12_0,
        D3D_FEATURE_LEVEL_11_1,
        D3D_FEATURE_LEVEL_11_0,
    };
    static const D3D_FEATURE_LEVEL d3d_9_x_feature_levels[] =
    {
        D3D_FEATURE_LEVEL_9_3,
        D3D_FEATURE_LEVEL_9_2,
        D3D_FEATURE_LEVEL_9_1,
    };
    static const D3D_FEATURE_LEVEL invalid_feature_levels[] =
    {
        0x0000,
        0x3000,
    };

    if (!(device = create_device()))
    {
        skip("Failed to create device.\n");
        return;
    }

    /* Architecture. */
    memset(&architecture, 0, sizeof(architecture));
    hr = ID3D12Device_CheckFeatureSupport(device, D3D12_FEATURE_ARCHITECTURE,
            &architecture, sizeof(architecture));
    ok(hr == S_OK, "Got unexpected hr %#x.\n", hr);
    ok(!architecture.NodeIndex, "Got unexpected node %u.\n", architecture.NodeIndex);
    ok(!architecture.CacheCoherentUMA || architecture.UMA,
            "Got unexpected cache coherent UMA %#x (UMA %#x).\n",
            architecture.CacheCoherentUMA, architecture.UMA);
    trace("UMA %#x, cache coherent UMA %#x, tile based renderer %#x.\n",
            architecture.UMA, architecture.CacheCoherentUMA, architecture.TileBasedRenderer);

    if (ID3D12Device_GetNodeCount(device) == 1)
    {
        memset(&architecture, 0, sizeof(architecture));
        architecture.NodeIndex = 1;
        hr = ID3D12Device_CheckFeatureSupport(device, D3D12_FEATURE_ARCHITECTURE,
                &architecture, sizeof(architecture));
        ok(hr == E_INVALIDARG, "Got unexpected hr %#x.\n", hr);
    }

    /* Feature levels */
    memset(&feature_levels, 0, sizeof(feature_levels));
    hr = ID3D12Device_CheckFeatureSupport(device, D3D12_FEATURE_FEATURE_LEVELS,
            &feature_levels, sizeof(feature_levels));
    ok(hr == E_INVALIDARG, "Got unexpected hr %#x.\n", hr);

    feature_levels.NumFeatureLevels = ARRAY_SIZE(all_feature_levels);
    feature_levels.pFeatureLevelsRequested = all_feature_levels;
    feature_levels.MaxSupportedFeatureLevel = 0;
    hr = ID3D12Device_CheckFeatureSupport(device, D3D12_FEATURE_FEATURE_LEVELS,
            &feature_levels, sizeof(feature_levels));
    ok(hr == S_OK, "Failed to check feature support, hr %#x.\n", hr);
    trace("Max supported feature level %#x.\n", feature_levels.MaxSupportedFeatureLevel);
    max_supported_feature_level = feature_levels.MaxSupportedFeatureLevel;

    feature_levels.NumFeatureLevels = ARRAY_SIZE(d3d12_feature_levels);
    feature_levels.pFeatureLevelsRequested = d3d12_feature_levels;
    feature_levels.MaxSupportedFeatureLevel = 0;
    hr = ID3D12Device_CheckFeatureSupport(device, D3D12_FEATURE_FEATURE_LEVELS,
            &feature_levels, sizeof(feature_levels));
    ok(hr == S_OK, "Failed to check feature support, hr %#x.\n", hr);
    ok(feature_levels.MaxSupportedFeatureLevel == max_supported_feature_level,
            "Got unexpected feature level %#x, expected %#x.\n",
            feature_levels.MaxSupportedFeatureLevel, max_supported_feature_level);

    /* Check invalid size. */
    hr = ID3D12Device_CheckFeatureSupport(device, D3D12_FEATURE_FEATURE_LEVELS,
            &feature_levels, sizeof(feature_levels) + 1);
    ok(hr == E_INVALIDARG, "Got unexpected hr %#x.\n", hr);
    hr = ID3D12Device_CheckFeatureSupport(device, D3D12_FEATURE_FEATURE_LEVELS,
            &feature_levels, sizeof(feature_levels) - 1);
    ok(hr == E_INVALIDARG, "Got unexpected hr %#x.\n", hr);

    feature_levels.NumFeatureLevels = ARRAY_SIZE(d3d_9_x_feature_levels);
    feature_levels.pFeatureLevelsRequested = d3d_9_x_feature_levels;
    feature_levels.MaxSupportedFeatureLevel = 0;
    hr = ID3D12Device_CheckFeatureSupport(device, D3D12_FEATURE_FEATURE_LEVELS,
            &feature_levels, sizeof(feature_levels));
    ok(hr == S_OK, "Failed to check feature support, hr %#x.\n", hr);
    ok(feature_levels.MaxSupportedFeatureLevel == D3D_FEATURE_LEVEL_9_3,
            "Got unexpected max feature level %#x.\n", feature_levels.MaxSupportedFeatureLevel);

    feature_levels.NumFeatureLevels = ARRAY_SIZE(invalid_feature_levels);
    feature_levels.pFeatureLevelsRequested = invalid_feature_levels;
    feature_levels.MaxSupportedFeatureLevel = 0;
    hr = ID3D12Device_CheckFeatureSupport(device, D3D12_FEATURE_FEATURE_LEVELS,
            &feature_levels, sizeof(feature_levels));
    ok(hr == S_OK, "Failed to check feature support, hr %#x.\n", hr);
    ok(feature_levels.MaxSupportedFeatureLevel == 0x3000,
            "Got unexpected max feature level %#x.\n", feature_levels.MaxSupportedFeatureLevel);

    /* Format info. */
    memset(&format_info, 0, sizeof(format_info));
    hr = ID3D12Device_CheckFeatureSupport(device, D3D12_FEATURE_FORMAT_INFO,
            &format_info, sizeof(format_info));
    ok(hr == S_OK, "Failed to get format info, hr %#x.\n", hr);
    ok(format_info.Format == DXGI_FORMAT_UNKNOWN, "Got unexpected format %#x.\n", format_info.Format);
    ok(format_info.PlaneCount == 1, "Got unexpected plane count %u.\n", format_info.PlaneCount);

    for (format = DXGI_FORMAT_UNKNOWN; format <= DXGI_FORMAT_B4G4R4A4_UNORM; ++format)
    {
        vkd3d_test_set_context("format %#x", format);

        switch (format)
        {
            case DXGI_FORMAT_R32G8X24_TYPELESS:
            case DXGI_FORMAT_D32_FLOAT_S8X24_UINT:
            case DXGI_FORMAT_R32_FLOAT_X8X24_TYPELESS:
            case DXGI_FORMAT_X32_TYPELESS_G8X24_UINT:
            case DXGI_FORMAT_D24_UNORM_S8_UINT:
            case DXGI_FORMAT_R24G8_TYPELESS:
            case DXGI_FORMAT_R24_UNORM_X8_TYPELESS:
            case DXGI_FORMAT_X24_TYPELESS_G8_UINT:
            case DXGI_FORMAT_NV12:
            case DXGI_FORMAT_P010:
            case DXGI_FORMAT_P016:
            case DXGI_FORMAT_NV11:
                expected_plane_count = 2;
                break;
            default:
                expected_plane_count = 1;
                break;
        }

        is_todo = format == DXGI_FORMAT_R8G8_B8G8_UNORM
                || format == DXGI_FORMAT_G8R8_G8B8_UNORM
                || format == DXGI_FORMAT_B5G6R5_UNORM
                || format == DXGI_FORMAT_B5G5R5A1_UNORM
                || format == DXGI_FORMAT_R10G10B10_XR_BIAS_A2_UNORM
                || (DXGI_FORMAT_AYUV <= format && format <= DXGI_FORMAT_B4G4R4A4_UNORM);

        memset(&format_info, 0, sizeof(format_info));
        format_info.Format = format;
        hr = ID3D12Device_CheckFeatureSupport(device, D3D12_FEATURE_FORMAT_INFO,
                &format_info, sizeof(format_info));

        if (format == DXGI_FORMAT_R1_UNORM)
        {
            ok(hr == E_INVALIDARG, "Got unexpected hr %#x.\n", hr);
            continue;
        }

        todo_if(is_todo)
        ok(hr == S_OK, "Failed to get format info, hr %#x.\n", hr);
        ok(format_info.Format == format, "Got unexpected format %#x.\n", format_info.Format);
        todo_if(is_todo)
        ok(format_info.PlaneCount == expected_plane_count,
                "Got plane count %u, expected %u.\n", format_info.PlaneCount, expected_plane_count);
    }
    vkd3d_test_set_context(NULL);

    /* GPU virtual address */
    memset(&gpu_virtual_address, 0, sizeof(gpu_virtual_address));
    hr = ID3D12Device_CheckFeatureSupport(device, D3D12_FEATURE_GPU_VIRTUAL_ADDRESS_SUPPORT,
            &gpu_virtual_address, sizeof(gpu_virtual_address));
    ok(hr == S_OK, "Failed to check GPU virtual address support, hr %#x.\n", hr);
    trace("GPU virtual address bits per resource: %u.\n",
            gpu_virtual_address.MaxGPUVirtualAddressBitsPerResource);
    trace("GPU virtual address bits per process: %u.\n",
            gpu_virtual_address.MaxGPUVirtualAddressBitsPerProcess);

    root_signature.HighestVersion = D3D_ROOT_SIGNATURE_VERSION_1_0;
    hr = ID3D12Device_CheckFeatureSupport(device, D3D12_FEATURE_ROOT_SIGNATURE,
            &root_signature, sizeof(root_signature));
    ok(hr == S_OK, "Failed to get root signature feature support, hr %#x.\n", hr);
    ok(root_signature.HighestVersion == D3D_ROOT_SIGNATURE_VERSION_1_0,
            "Got unexpected root signature feature version %#x.\n", root_signature.HighestVersion);

    root_signature.HighestVersion = D3D_ROOT_SIGNATURE_VERSION_1_1;
    hr = ID3D12Device_CheckFeatureSupport(device, D3D12_FEATURE_ROOT_SIGNATURE,
            &root_signature, sizeof(root_signature));
    ok(hr == S_OK, "Failed to get root signature feature support, hr %#x.\n", hr);
    ok(root_signature.HighestVersion == D3D_ROOT_SIGNATURE_VERSION_1_0
            || root_signature.HighestVersion == D3D_ROOT_SIGNATURE_VERSION_1_1,
            "Got unexpected root signature feature version %#x.\n", root_signature.HighestVersion);

    root_signature.HighestVersion = 0;
    hr = ID3D12Device_CheckFeatureSupport(device, D3D12_FEATURE_ROOT_SIGNATURE,
            &root_signature, sizeof(root_signature));
    ok(hr == E_INVALIDARG, "Got unexpected hr %#x.\n", hr);
    ok(root_signature.HighestVersion == 0, "Got unexpected root signature feature version %#x.\n",
            root_signature.HighestVersion);

    root_signature.HighestVersion = 0xdeadbeef;
    hr = ID3D12Device_CheckFeatureSupport(device, D3D12_FEATURE_ROOT_SIGNATURE,
            &root_signature, sizeof(root_signature));
    ok(hr == E_INVALIDARG, "Got unexpected hr %#x.\n", hr);
    ok(root_signature.HighestVersion == 0xdeadbeef, "Got unexpected root signature feature version %#x.\n",
            root_signature.HighestVersion);

    refcount = ID3D12Device_Release(device);
    ok(!refcount, "ID3D12Device has %u references left.\n", (unsigned int)refcount);
}

static const DXGI_FORMAT depth_stencil_formats[] =
{
    DXGI_FORMAT_R32G8X24_TYPELESS,
    DXGI_FORMAT_D32_FLOAT_S8X24_UINT,
    DXGI_FORMAT_R32_FLOAT_X8X24_TYPELESS,
    DXGI_FORMAT_X32_TYPELESS_G8X24_UINT,
    DXGI_FORMAT_R32_TYPELESS,
    DXGI_FORMAT_D32_FLOAT,
    DXGI_FORMAT_R24G8_TYPELESS,
    DXGI_FORMAT_D24_UNORM_S8_UINT,
    DXGI_FORMAT_R24_UNORM_X8_TYPELESS,
    DXGI_FORMAT_X24_TYPELESS_G8_UINT,
    DXGI_FORMAT_R16_TYPELESS,
    DXGI_FORMAT_D16_UNORM,
};

void test_format_support(void)
{
    D3D12_FEATURE_DATA_FORMAT_SUPPORT format_support;
    D3D12_FEATURE_DATA_FEATURE_LEVELS feature_levels;
    bool required_but_fails;
    bool unspecified_format;
    bool optional_format;
    ID3D12Device *device;
    DXGI_FORMAT format;
    ULONG refcount;
    unsigned int i;
    HRESULT hr;

    static const DXGI_FORMAT known_required_but_fails[] =
    {
        DXGI_FORMAT_R8G8_B8G8_UNORM,
        DXGI_FORMAT_G8R8_G8B8_UNORM,
        DXGI_FORMAT_R10G10B10_XR_BIAS_A2_UNORM,
        DXGI_FORMAT_420_OPAQUE,
        DXGI_FORMAT_YUY2,
    };

#define MAX_FORMAT_VALUE DXGI_FORMAT_SAMPLER_FEEDBACK_MIP_REGION_USED_OPAQUE

    const struct dxgi_format_list
    {
        D3D_FEATURE_LEVEL feature_level;
        DXGI_FORMAT optional_formats[MAX_FORMAT_VALUE];
        DXGI_FORMAT unspecified_formats[MAX_FORMAT_VALUE];
    } *dxgi_format, dxgi_format_list[] = {
        /*
            https://devblogs.microsoft.com/directx/new-in-directx-feature-level-12_2/
            https://microsoft.github.io/DirectX-Specs/d3d/SamplerFeedback.html
        */
        {D3D_FEATURE_LEVEL_12_2, 
            {DXGI_FORMAT_AYUV, DXGI_FORMAT_Y410, DXGI_FORMAT_Y416, DXGI_FORMAT_P010, DXGI_FORMAT_P016, DXGI_FORMAT_Y210, DXGI_FORMAT_Y216, DXGI_FORMAT_NV11,
                DXGI_FORMAT_AI44, DXGI_FORMAT_IA44, DXGI_FORMAT_P8, DXGI_FORMAT_A8P8},
            {DXGI_FORMAT_R1_UNORM, DXGI_FORMAT_B4G4R4A4_UNORM, DXGI_FORMAT_P208, DXGI_FORMAT_V208, DXGI_FORMAT_V408}},
        /* https://github.com/MicrosoftDocs/win32/blob/docs/desktop-src/direct3ddxgi/hardware-support-for-direct3d-12-1-formats.md */
        {D3D_FEATURE_LEVEL_12_1, 
            {DXGI_FORMAT_AYUV, DXGI_FORMAT_Y410, DXGI_FORMAT_Y416, DXGI_FORMAT_P010, DXGI_FORMAT_P016, DXGI_FORMAT_Y210, DXGI_FORMAT_Y216, DXGI_FORMAT_NV11,
                DXGI_FORMAT_AI44, DXGI_FORMAT_IA44, DXGI_FORMAT_P8, DXGI_FORMAT_A8P8},
            {DXGI_FORMAT_R1_UNORM, DXGI_FORMAT_B4G4R4A4_UNORM, DXGI_FORMAT_P208, DXGI_FORMAT_V208, DXGI_FORMAT_V408, 
                DXGI_FORMAT_SAMPLER_FEEDBACK_MIN_MIP_OPAQUE, DXGI_FORMAT_SAMPLER_FEEDBACK_MIP_REGION_USED_OPAQUE}},
        /* https://github.com/MicrosoftDocs/win32/blob/docs/desktop-src/direct3ddxgi/hardware-support-for-direct3d-12-0-formats.md */
        {D3D_FEATURE_LEVEL_12_0, 
            {DXGI_FORMAT_AYUV, DXGI_FORMAT_Y410, DXGI_FORMAT_Y416, DXGI_FORMAT_P010, DXGI_FORMAT_P016, DXGI_FORMAT_Y210, DXGI_FORMAT_Y216, DXGI_FORMAT_NV11,
                DXGI_FORMAT_AI44, DXGI_FORMAT_IA44, DXGI_FORMAT_P8, DXGI_FORMAT_A8P8},
            {DXGI_FORMAT_R1_UNORM, DXGI_FORMAT_P208, DXGI_FORMAT_V208, DXGI_FORMAT_V408, 
                DXGI_FORMAT_SAMPLER_FEEDBACK_MIN_MIP_OPAQUE, DXGI_FORMAT_SAMPLER_FEEDBACK_MIP_REGION_USED_OPAQUE}},
        /* https://github.com/MicrosoftDocs/win32/blob/docs/desktop-src/direct3ddxgi/format-support-for-direct3d-11-1-feature-level-hardware.md */
        {D3D_FEATURE_LEVEL_11_1, 
            {DXGI_FORMAT_AYUV, DXGI_FORMAT_Y410, DXGI_FORMAT_Y416, DXGI_FORMAT_P010, DXGI_FORMAT_P016, DXGI_FORMAT_Y210, DXGI_FORMAT_Y216, DXGI_FORMAT_NV11,
                DXGI_FORMAT_AI44, DXGI_FORMAT_IA44, DXGI_FORMAT_P8, DXGI_FORMAT_A8P8}, 
            {DXGI_FORMAT_R1_UNORM, DXGI_FORMAT_P208, DXGI_FORMAT_V208, DXGI_FORMAT_V408, 
                DXGI_FORMAT_SAMPLER_FEEDBACK_MIN_MIP_OPAQUE, DXGI_FORMAT_SAMPLER_FEEDBACK_MIP_REGION_USED_OPAQUE}},
        /* https://github.com/MicrosoftDocs/win32/blob/docs/desktop-src/direct3ddxgi/format-support-for-direct3d-11-0-feature-level-hardware.md */
        {D3D_FEATURE_LEVEL_11_0, 
            {DXGI_FORMAT_AYUV, DXGI_FORMAT_Y410, DXGI_FORMAT_Y416, DXGI_FORMAT_P010, DXGI_FORMAT_P016, DXGI_FORMAT_Y210, DXGI_FORMAT_Y216, DXGI_FORMAT_NV11,
                DXGI_FORMAT_AI44, DXGI_FORMAT_IA44, DXGI_FORMAT_P8, DXGI_FORMAT_A8P8}, 
            {DXGI_FORMAT_R1_UNORM, DXGI_FORMAT_P208, DXGI_FORMAT_V208, DXGI_FORMAT_V408, 
                DXGI_FORMAT_SAMPLER_FEEDBACK_MIN_MIP_OPAQUE, DXGI_FORMAT_SAMPLER_FEEDBACK_MIP_REGION_USED_OPAQUE}},
        /* https://github.com/MicrosoftDocs/win32/blob/docs/desktop-src/direct3ddxgi/format-support-for-direct3d-feature-level-10-1-hardware.md */
        {D3D_FEATURE_LEVEL_10_1, 
            {DXGI_FORMAT_AYUV, DXGI_FORMAT_Y410, DXGI_FORMAT_Y416, DXGI_FORMAT_P010, DXGI_FORMAT_P016, DXGI_FORMAT_Y210, 
                DXGI_FORMAT_Y216, DXGI_FORMAT_NV11, DXGI_FORMAT_AI44, DXGI_FORMAT_IA44, DXGI_FORMAT_P8, DXGI_FORMAT_A8P8}, 
            {DXGI_FORMAT_R1_UNORM, DXGI_FORMAT_P208, DXGI_FORMAT_V208, DXGI_FORMAT_V408, 
                DXGI_FORMAT_SAMPLER_FEEDBACK_MIN_MIP_OPAQUE, DXGI_FORMAT_SAMPLER_FEEDBACK_MIP_REGION_USED_OPAQUE}},
        /* https://github.com/MicrosoftDocs/win32/blob/docs/desktop-src/direct3ddxgi/format-support-for-direct3d-feature-level-10-1-hardware.md */
        {D3D_FEATURE_LEVEL_10_0, 
            {DXGI_FORMAT_R10G10B10_XR_BIAS_A2_UNORM, DXGI_FORMAT_AYUV, DXGI_FORMAT_Y410, DXGI_FORMAT_Y416, DXGI_FORMAT_P010, DXGI_FORMAT_P016, 
                DXGI_FORMAT_Y210, DXGI_FORMAT_Y216, DXGI_FORMAT_NV11, DXGI_FORMAT_AI44, DXGI_FORMAT_IA44, DXGI_FORMAT_P8, DXGI_FORMAT_A8P8}, 
            {DXGI_FORMAT_R1_UNORM, DXGI_FORMAT_P208, DXGI_FORMAT_V208, DXGI_FORMAT_V408, 
                DXGI_FORMAT_SAMPLER_FEEDBACK_MIN_MIP_OPAQUE, DXGI_FORMAT_SAMPLER_FEEDBACK_MIP_REGION_USED_OPAQUE}},
    };

#undef MAX_FORMAT_VALUE

    static const D3D_FEATURE_LEVEL all_feature_levels[] =
    {
        D3D_FEATURE_LEVEL_12_2,
        D3D_FEATURE_LEVEL_12_1,
        D3D_FEATURE_LEVEL_12_0,
        D3D_FEATURE_LEVEL_11_1,
        D3D_FEATURE_LEVEL_11_0,
        D3D_FEATURE_LEVEL_10_1,
        D3D_FEATURE_LEVEL_10_0,
        D3D_FEATURE_LEVEL_9_3,
        D3D_FEATURE_LEVEL_9_2,
        D3D_FEATURE_LEVEL_9_1,
    };

    if (!(device = create_device()))
    {
        skip("Failed to create device.\n");
        return;
    }

    memset(&format_support, 0, sizeof(format_support));
    hr = ID3D12Device_CheckFeatureSupport(device, D3D12_FEATURE_FORMAT_SUPPORT,
            &format_support, sizeof(format_support));
    ok(hr == S_OK, "Got unexpected hr %#x.\n", hr);
    ok(format_support.Support1 == D3D12_FORMAT_SUPPORT1_BUFFER,
            "Got unexpected support1 %#x.\n", format_support.Support1);
    ok(!format_support.Support2 || format_support.Support2 == D3D12_FORMAT_SUPPORT2_TILED,
            "Got unexpected support2 %#x.\n", format_support.Support2);

    memset(&feature_levels, 0, sizeof(feature_levels));
    feature_levels.NumFeatureLevels = ARRAY_SIZE(all_feature_levels);
    feature_levels.pFeatureLevelsRequested = all_feature_levels;
    hr = ID3D12Device_CheckFeatureSupport(device, D3D12_FEATURE_FEATURE_LEVELS, 
        &feature_levels, sizeof(feature_levels));
    ok(hr == S_OK, "Got unexpected hr %#x.\n", hr);

    dxgi_format = NULL;
    for (i = 0; i < ARRAY_SIZE(dxgi_format_list); ++i)
    {
        if (dxgi_format_list[i].feature_level == feature_levels.MaxSupportedFeatureLevel)
        {
            dxgi_format = &dxgi_format_list[i];
            break;
        }
    }
    assert(dxgi_format);

    for (format = 0; format <= DXGI_FORMAT_SAMPLER_FEEDBACK_MIP_REGION_USED_OPAQUE; ++format)
    {
        /* Undefined range, skip */
        if (format > DXGI_FORMAT_B4G4R4A4_UNORM && format < DXGI_FORMAT_P208)
            continue;

        vkd3d_test_set_context("format %#x", format);

        required_but_fails = false;
        unspecified_format = false;
        optional_format = false;

        for (i = 0; i < ARRAY_SIZE(known_required_but_fails); ++i)
        {
            if (known_required_but_fails[i] == format)
            {
                required_but_fails = true;
                break;
            }
        }

        if (!required_but_fails)
        {
            /* ASTC format are unspecified */
            if (format >= /* DXGI_FORMAT_UNDOCUMENTED_ASTC_FIRST */ (DXGI_FORMAT)0x85 && 
                format <= /* DXGI_FORMAT_UNDOCUMENTED_ASTC_LAST */  (DXGI_FORMAT)0xbc)
            {
                unspecified_format = true;
            }
            else
            {
                /* Check if the format is unspecified or optional */
                for (i = 0; i < ARRAY_SIZE(dxgi_format->unspecified_formats); ++i)
                {
                    /* Fixed size list with only part of the list filled */
                    if (dxgi_format->unspecified_formats[i] == DXGI_FORMAT_UNKNOWN)
                        break;
                    if (dxgi_format->unspecified_formats[i] == format)
                    {
                        unspecified_format = true;
                        break;
                    }
                }

                if (!unspecified_format)
                {
                    for (i = 0; i < ARRAY_SIZE(dxgi_format->optional_formats); ++i)
                    {
                        /* Fixed size list with only part of the list filled */
                        if (dxgi_format->optional_formats[i] == DXGI_FORMAT_UNKNOWN)
                            break;
                        if (dxgi_format->optional_formats[i] == format)
                        {
                            optional_format = true;
                            break;
                        }
                    }
                }
            }
        }

        memset(&format_support, 0, sizeof(format_support));
        format_support.Format = format;
        hr = ID3D12Device_CheckFeatureSupport(device, D3D12_FEATURE_FORMAT_SUPPORT,
                &format_support, sizeof(format_support));

        if (unspecified_format)
            ok(hr == S_OK || hr == E_FAIL, "Unspecified format %d got unexpected hr %#x.\n", format, hr);
        else if (optional_format)
            ok(hr == S_OK || hr == E_FAIL, "Optional format %d got unexpected hr %#x.\n", format, hr);
        else 
            todo_if(required_but_fails) ok(hr == S_OK, "Format %d got unexpected hr %#x.\n", format, hr);
    }
    vkd3d_test_set_context(NULL);

    refcount = ID3D12Device_Release(device);
    ok(!refcount, "ID3D12Device has %u references left.\n", (unsigned int)refcount);
}

void test_multisample_quality_levels(void)
{
    static const unsigned int sample_counts[] = {1, 2, 4, 8, 16, 32};
    D3D12_FEATURE_DATA_MULTISAMPLE_QUALITY_LEVELS format_support;
    ID3D12Device *device;
    DXGI_FORMAT format;
    unsigned int i, j;
    ULONG refcount;
    HRESULT hr;

    if (!(device = create_device()))
    {
        skip("Failed to create device.\n");
        return;
    }

    memset(&format_support, 0, sizeof(format_support));
    format_support.NumQualityLevels = 0xdeadbeef;
    hr = ID3D12Device_CheckFeatureSupport(device, D3D12_FEATURE_MULTISAMPLE_QUALITY_LEVELS,
            &format_support, sizeof(format_support));
    ok(hr == E_FAIL, "Got unexpected hr %#x.\n", hr);
    ok(!format_support.Flags, "Got unexpected flags %#x.\n", format_support.Flags);
    ok(!format_support.NumQualityLevels, "Got unexpected quality levels %u.\n", format_support.NumQualityLevels);

    format_support.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
    format_support.NumQualityLevels = 0xdeadbeef;
    hr = ID3D12Device_CheckFeatureSupport(device, D3D12_FEATURE_MULTISAMPLE_QUALITY_LEVELS,
            &format_support, sizeof(format_support));
    ok(hr == E_FAIL, "Got unexpected hr %#x.\n", hr);
    ok(!format_support.Flags, "Got unexpected flags %#x.\n", format_support.Flags);
    ok(!format_support.NumQualityLevels, "Got unexpected quality levels %u.\n", format_support.NumQualityLevels);

    /* 1 sample */
    for (format = DXGI_FORMAT_UNKNOWN; format <= DXGI_FORMAT_B4G4R4A4_UNORM; ++format)
    {
        if (format == DXGI_FORMAT_R1_UNORM)
            continue;

        vkd3d_test_set_context("format %#x", format);

        memset(&format_support, 0, sizeof(format_support));
        format_support.Format = format;
        format_support.SampleCount = 1;
        format_support.NumQualityLevels = 0xdeadbeef;
        hr = ID3D12Device_CheckFeatureSupport(device, D3D12_FEATURE_MULTISAMPLE_QUALITY_LEVELS,
                &format_support, sizeof(format_support));
        ok(hr == S_OK, "Got unexpected hr %#x.\n", hr);
        ok(format_support.NumQualityLevels == 1, "Got unexpected quality levels %u.\n", format_support.NumQualityLevels);
    }
    vkd3d_test_set_context(NULL);

    /* DXGI_FORMAT_UNKNOWN */
    for (i = 1; i < ARRAY_SIZE(sample_counts); ++i)
    {
        vkd3d_test_set_context("samples %#x", sample_counts[i]);

        memset(&format_support, 0, sizeof(format_support));
        format_support.SampleCount = sample_counts[i];
        format_support.NumQualityLevels = 0xdeadbeef;
        hr = ID3D12Device_CheckFeatureSupport(device, D3D12_FEATURE_MULTISAMPLE_QUALITY_LEVELS,
                &format_support, sizeof(format_support));
        ok(hr == S_OK, "Got unexpected hr %#x.\n", hr);
        ok(!format_support.Flags, "Got unexpected flags %#x.\n", format_support.Flags);
        ok(!format_support.NumQualityLevels, "Got unexpected quality levels %u.\n", format_support.NumQualityLevels);

        format_support.Flags = D3D12_MULTISAMPLE_QUALITY_LEVELS_FLAG_TILED_RESOURCE;
        format_support.NumQualityLevels = 0xdeadbeef;
        hr = ID3D12Device_CheckFeatureSupport(device, D3D12_FEATURE_MULTISAMPLE_QUALITY_LEVELS,
                &format_support, sizeof(format_support));
        ok(hr == S_OK, "Got unexpected hr %#x.\n", hr);
        ok(format_support.Flags == D3D12_MULTISAMPLE_QUALITY_LEVELS_FLAG_TILED_RESOURCE,
                "Got unexpected flags %#x.\n", format_support.Flags);
        ok(!format_support.NumQualityLevels, "Got unexpected quality levels %u.\n", format_support.NumQualityLevels);
    }
    vkd3d_test_set_context(NULL);

    /* invalid sample counts */
    for (i = 1; i <= 32; ++i)
    {
        bool valid_sample_count = false;
        for (j = 0; j < ARRAY_SIZE(sample_counts); ++j)
        {
            if (sample_counts[j] == i)
            {
                valid_sample_count = true;
                break;
            }
        }
        if (valid_sample_count)
            continue;

        vkd3d_test_set_context("samples %#x", i);

        memset(&format_support, 0, sizeof(format_support));
        format_support.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
        format_support.SampleCount = i;
        format_support.NumQualityLevels = 0xdeadbeef;
        hr = ID3D12Device_CheckFeatureSupport(device, D3D12_FEATURE_MULTISAMPLE_QUALITY_LEVELS,
                &format_support, sizeof(format_support));
        ok(hr == S_OK, "Got unexpected hr %#x.\n", hr);
        ok(!format_support.Flags, "Got unexpected flags %#x.\n", format_support.Flags);
        ok(!format_support.NumQualityLevels, "Got unexpected quality levels %u.\n", format_support.NumQualityLevels);
    }
    vkd3d_test_set_context(NULL);

    /* DXGI_FORMAT_R8G8B8A8_UNORM */
    memset(&format_support, 0, sizeof(format_support));
    format_support.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
    format_support.SampleCount = 4;
    format_support.NumQualityLevels = 0xdeadbeef;
    hr = ID3D12Device_CheckFeatureSupport(device, D3D12_FEATURE_MULTISAMPLE_QUALITY_LEVELS,
            &format_support, sizeof(format_support));
    ok(hr == S_OK, "Got unexpected hr %#x.\n", hr);
    ok(!format_support.Flags, "Got unexpected flags %#x.\n", format_support.Flags);
    ok(format_support.NumQualityLevels >= 1, "Got unexpected quality levels %u.\n", format_support.NumQualityLevels);

    /* DXGI_FORMAT_D32_FLOAT_S8X24_UINT */
    format_support.Format = DXGI_FORMAT_D32_FLOAT_S8X24_UINT;
    format_support.SampleCount = 4;
    format_support.NumQualityLevels = 0xdeadbeef;
    hr = ID3D12Device_CheckFeatureSupport(device, D3D12_FEATURE_MULTISAMPLE_QUALITY_LEVELS,
            &format_support, sizeof(format_support));
    ok(hr == S_OK, "Got unexpected hr %#x.\n", hr);
    ok(format_support.NumQualityLevels >= 1, "Got unexpected quality levels %u.\n", format_support.NumQualityLevels);

    for (i = 0; i < ARRAY_SIZE(depth_stencil_formats); ++i)
    {
        memset(&format_support, 0, sizeof(format_support));
        format_support.Format = depth_stencil_formats[i];
        format_support.SampleCount = 4;
        format_support.NumQualityLevels = 0xdeadbeef;
        hr = ID3D12Device_CheckFeatureSupport(device, D3D12_FEATURE_MULTISAMPLE_QUALITY_LEVELS,
                &format_support, sizeof(format_support));
        ok(hr == S_OK, "Got unexpected hr %#x.\n", hr);
    }

    refcount = ID3D12Device_Release(device);
    ok(!refcount, "ID3D12Device has %u references left.\n", (unsigned int)refcount);
}

void test_create_command_allocator(void)
{
    ID3D12CommandAllocator *command_allocator;
    ID3D12Device *device, *tmp_device;
    ULONG refcount;
    HRESULT hr;

    if (!(device = create_device()))
    {
        skip("Failed to create device.\n");
        return;
    }

    hr = ID3D12Device_CreateCommandAllocator(device, D3D12_COMMAND_LIST_TYPE_DIRECT,
            &IID_ID3D12CommandAllocator, (void **)&command_allocator);
    ok(SUCCEEDED(hr), "Failed to create command allocator, hr %#x.\n", hr);

    refcount = get_refcount(device);
    ok(refcount == 2, "Got unexpected refcount %u.\n", (unsigned int)refcount);
    hr = ID3D12CommandAllocator_GetDevice(command_allocator, &IID_ID3D12Device, (void **)&tmp_device);
    ok(SUCCEEDED(hr), "Failed to get device, hr %#x.\n", hr);
    refcount = get_refcount(device);
    ok(refcount == 3, "Got unexpected refcount %u.\n", (unsigned int)refcount);
    refcount = ID3D12Device_Release(tmp_device);
    ok(refcount == 2, "Got unexpected refcount %u.\n", (unsigned int)refcount);

    check_interface(command_allocator, &IID_ID3D12Object, true);
    check_interface(command_allocator, &IID_ID3D12DeviceChild, true);
    check_interface(command_allocator, &IID_ID3D12Pageable, true);
    check_interface(command_allocator, &IID_ID3D12CommandAllocator, true);

    refcount = ID3D12CommandAllocator_Release(command_allocator);
    ok(!refcount, "ID3D12CommandAllocator has %u references left.\n", (unsigned int)refcount);

    hr = ID3D12Device_CreateCommandAllocator(device, D3D12_COMMAND_LIST_TYPE_BUNDLE,
            &IID_ID3D12CommandAllocator, (void **)&command_allocator);
    ok(SUCCEEDED(hr), "Failed to create command allocator, hr %#x.\n", hr);
    refcount = ID3D12CommandAllocator_Release(command_allocator);
    ok(!refcount, "ID3D12CommandAllocator has %u references left.\n", (unsigned int)refcount);

    hr = ID3D12Device_CreateCommandAllocator(device, D3D12_COMMAND_LIST_TYPE_COMPUTE,
            &IID_ID3D12CommandAllocator, (void **)&command_allocator);
    ok(SUCCEEDED(hr), "Failed to create command allocator, hr %#x.\n", hr);
    refcount = ID3D12CommandAllocator_Release(command_allocator);
    ok(!refcount, "ID3D12CommandAllocator has %u references left.\n", (unsigned int)refcount);

    hr = ID3D12Device_CreateCommandAllocator(device, D3D12_COMMAND_LIST_TYPE_COPY,
            &IID_ID3D12CommandAllocator, (void **)&command_allocator);
    ok(SUCCEEDED(hr), "Failed to create command allocator, hr %#x.\n", hr);
    refcount = ID3D12CommandAllocator_Release(command_allocator);
    ok(!refcount, "ID3D12CommandAllocator has %u references left.\n", (unsigned int)refcount);

    hr = ID3D12Device_CreateCommandAllocator(device, ~0u,
            &IID_ID3D12CommandAllocator, (void **)&command_allocator);
    ok(hr == E_INVALIDARG, "Got unexpected hr %#x.\n", hr);

    refcount = ID3D12Device_Release(device);
    ok(!refcount, "ID3D12Device has %u references left.\n", (unsigned int)refcount);
}

void test_create_command_list(void)
{
    ID3D12CommandAllocator *command_allocator;
    ID3D12Device *device, *tmp_device;
    ID3D12CommandList *command_list;
    ULONG refcount;
    HRESULT hr;

    if (!(device = create_device()))
    {
        skip("Failed to create device.\n");
        return;
    }

    hr = ID3D12Device_CreateCommandList(device, 0, D3D12_COMMAND_LIST_TYPE_DIRECT,
            NULL, NULL, &IID_ID3D12CommandList, (void **)&command_list);
    ok(hr == E_INVALIDARG, "Got unexpected hr %#x.\n", hr);

    hr = ID3D12Device_CreateCommandAllocator(device, D3D12_COMMAND_LIST_TYPE_DIRECT,
            &IID_ID3D12CommandAllocator, (void **)&command_allocator);
    ok(SUCCEEDED(hr), "Failed to create command allocator, hr %#x.\n", hr);

    refcount = get_refcount(device);
    ok(refcount == 2, "Got unexpected refcount %u.\n", (unsigned int)refcount);

    hr = ID3D12Device_CreateCommandList(device, 0, D3D12_COMMAND_LIST_TYPE_DIRECT,
            command_allocator, NULL, &IID_ID3D12CommandList, (void **)&command_list);
    ok(SUCCEEDED(hr), "Failed to create command list, hr %#x.\n", hr);

    refcount = get_refcount(command_allocator);
    ok(refcount == 1, "Got unexpected refcount %u.\n", (unsigned int)refcount);

    refcount = get_refcount(device);
    ok(refcount == 3, "Got unexpected refcount %u.\n", (unsigned int)refcount);
    hr = ID3D12CommandList_GetDevice(command_list, &IID_ID3D12Device, (void **)&tmp_device);
    ok(SUCCEEDED(hr), "Failed to get device, hr %#x.\n", hr);
    refcount = get_refcount(device);
    ok(refcount == 4, "Got unexpected refcount %u.\n", (unsigned int)refcount);
    refcount = ID3D12Device_Release(tmp_device);
    ok(refcount == 3, "Got unexpected refcount %u.\n", (unsigned int)refcount);

    check_interface(command_list, &IID_ID3D12Object, true);
    check_interface(command_list, &IID_ID3D12DeviceChild, true);
    check_interface(command_list, &IID_ID3D12Pageable, false);
    check_interface(command_list, &IID_ID3D12CommandList, true);
    check_interface(command_list, &IID_ID3D12GraphicsCommandList, true);
    check_interface(command_list, &IID_ID3D12CommandAllocator, false);

    refcount = ID3D12CommandList_Release(command_list);
    ok(!refcount, "ID3D12CommandList has %u references left.\n", (unsigned int)refcount);
    refcount = ID3D12CommandAllocator_Release(command_allocator);
    ok(!refcount, "ID3D12CommandAllocator has %u references left.\n", (unsigned int)refcount);

    hr = ID3D12Device_CreateCommandAllocator(device, D3D12_COMMAND_LIST_TYPE_BUNDLE,
            &IID_ID3D12CommandAllocator, (void **)&command_allocator);
    ok(SUCCEEDED(hr), "Failed to create command allocator, hr %#x.\n", hr);
    hr = ID3D12Device_CreateCommandList(device, 0, D3D12_COMMAND_LIST_TYPE_DIRECT,
            command_allocator, NULL, &IID_ID3D12CommandList, (void **)&command_list);
    ok(hr == E_INVALIDARG, "Got unexpected hr %#x.\n", hr);
    hr = ID3D12Device_CreateCommandList(device, 0, D3D12_COMMAND_LIST_TYPE_BUNDLE,
            command_allocator, NULL, &IID_ID3D12CommandList, (void **)&command_list);
    ok(SUCCEEDED(hr), "Failed to create command list, hr %#x.\n", hr);
    check_interface(command_list, &IID_ID3D12GraphicsCommandList, true);
    refcount = ID3D12CommandList_Release(command_list);
    ok(!refcount, "ID3D12CommandList has %u references left.\n", (unsigned int)refcount);
    refcount = ID3D12CommandAllocator_Release(command_allocator);
    ok(!refcount, "ID3D12CommandAllocator has %u references left.\n", (unsigned int)refcount);

    hr = ID3D12Device_CreateCommandAllocator(device, D3D12_COMMAND_LIST_TYPE_COMPUTE,
            &IID_ID3D12CommandAllocator, (void **)&command_allocator);
    ok(SUCCEEDED(hr), "Failed to create command allocator, hr %#x.\n", hr);
    hr = ID3D12Device_CreateCommandList(device, 0, D3D12_COMMAND_LIST_TYPE_BUNDLE,
            command_allocator, NULL, &IID_ID3D12CommandList, (void **)&command_list);
    ok(hr == E_INVALIDARG, "Got unexpected hr %#x.\n", hr);
    hr = ID3D12Device_CreateCommandList(device, 0, D3D12_COMMAND_LIST_TYPE_COMPUTE,
            command_allocator, NULL, &IID_ID3D12CommandList, (void **)&command_list);
    ok(SUCCEEDED(hr), "Failed to create command list, hr %#x.\n", hr);
    check_interface(command_list, &IID_ID3D12GraphicsCommandList, true);
    refcount = ID3D12CommandList_Release(command_list);
    ok(!refcount, "ID3D12CommandList has %u references left.\n", (unsigned int)refcount);
    refcount = ID3D12CommandAllocator_Release(command_allocator);
    ok(!refcount, "ID3D12CommandAllocator has %u references left.\n", (unsigned int)refcount);

    hr = ID3D12Device_CreateCommandAllocator(device, D3D12_COMMAND_LIST_TYPE_COPY,
            &IID_ID3D12CommandAllocator, (void **)&command_allocator);
    ok(SUCCEEDED(hr), "Failed to create command allocator, hr %#x.\n", hr);
    hr = ID3D12Device_CreateCommandList(device, 0, D3D12_COMMAND_LIST_TYPE_DIRECT,
            command_allocator, NULL, &IID_ID3D12CommandList, (void **)&command_list);
    ok(hr == E_INVALIDARG, "Got unexpected hr %#x.\n", hr);
    hr = ID3D12Device_CreateCommandList(device, 0, D3D12_COMMAND_LIST_TYPE_COMPUTE,
            command_allocator, NULL, &IID_ID3D12CommandList, (void **)&command_list);
    ok(hr == E_INVALIDARG, "Got unexpected hr %#x.\n", hr);
    hr = ID3D12Device_CreateCommandList(device, 0, D3D12_COMMAND_LIST_TYPE_COPY,
            command_allocator, NULL, &IID_ID3D12CommandList, (void **)&command_list);
    ok(SUCCEEDED(hr), "Failed to create command list, hr %#x.\n", hr);
    check_interface(command_list, &IID_ID3D12GraphicsCommandList, true);
    refcount = ID3D12CommandList_Release(command_list);
    ok(!refcount, "ID3D12CommandList has %u references left.\n", (unsigned int)refcount);
    refcount = ID3D12CommandAllocator_Release(command_allocator);
    ok(!refcount, "ID3D12CommandAllocator has %u references left.\n", (unsigned int)refcount);

    refcount = ID3D12Device_Release(device);
    ok(!refcount, "ID3D12Device has %u references left.\n", (unsigned int)refcount);
}

void test_create_command_queue(void)
{
    ID3D12CommandQueue* direct_queues[8], *compute_queues[8];
    D3D12_COMMAND_QUEUE_DESC desc, result_desc;
    ID3D12Device *device, *tmp_device;
    ID3D12CommandQueue *queue;
    unsigned int i;
    ULONG refcount;
    HRESULT hr;

    if (!(device = create_device()))
    {
        skip("Failed to create device.\n");
        return;
    }

    desc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;
    desc.Priority = D3D12_COMMAND_QUEUE_PRIORITY_NORMAL;
    desc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE;
    desc.NodeMask = 0;
    hr = ID3D12Device_CreateCommandQueue(device, &desc, &IID_ID3D12CommandQueue, (void **)&queue);
    ok(SUCCEEDED(hr), "Failed to create command queue, hr %#x.\n", hr);

    refcount = get_refcount(device);
    ok(refcount == 2, "Got unexpected refcount %u.\n", (unsigned int)refcount);
    hr = ID3D12CommandQueue_GetDevice(queue, &IID_ID3D12Device, (void **)&tmp_device);
    ok(SUCCEEDED(hr), "Failed to get device, hr %#x.\n", hr);
    refcount = get_refcount(device);
    ok(refcount == 3, "Got unexpected refcount %u.\n", (unsigned int)refcount);
    refcount = ID3D12Device_Release(tmp_device);
    ok(refcount == 2, "Got unexpected refcount %u.\n", (unsigned int)refcount);

    check_interface(queue, &IID_ID3D12Object, true);
    check_interface(queue, &IID_ID3D12DeviceChild, true);
    check_interface(queue, &IID_ID3D12Pageable, true);
    check_interface(queue, &IID_ID3D12CommandQueue, true);

    result_desc = ID3D12CommandQueue_GetDesc(queue);
    ok(result_desc.Type == desc.Type, "Got unexpected type %#x.\n", result_desc.Type);
    ok(result_desc.Priority == desc.Priority, "Got unexpected priority %#x.\n", result_desc.Priority);
    ok(result_desc.Flags == desc.Flags, "Got unexpected flags %#x.\n", result_desc.Flags);
    ok(result_desc.NodeMask == 0x1, "Got unexpected node mask 0x%08x.\n", result_desc.NodeMask);

    refcount = ID3D12CommandQueue_Release(queue);
    ok(!refcount, "ID3D12CommandQueue has %u references left.\n", (unsigned int)refcount);

    desc.Type = D3D12_COMMAND_LIST_TYPE_COMPUTE;
    hr = ID3D12Device_CreateCommandQueue(device, &desc, &IID_ID3D12CommandQueue, (void **)&queue);
    ok(SUCCEEDED(hr), "Failed to create command queue, hr %#x.\n", hr);

    result_desc = ID3D12CommandQueue_GetDesc(queue);
    ok(result_desc.Type == desc.Type, "Got unexpected type %#x.\n", result_desc.Type);
    ok(result_desc.Priority == desc.Priority, "Got unexpected priority %#x.\n", result_desc.Priority);
    ok(result_desc.Flags == desc.Flags, "Got unexpected flags %#x.\n", result_desc.Flags);
    ok(result_desc.NodeMask == 0x1, "Got unexpected node mask 0x%08x.\n", result_desc.NodeMask);

    refcount = ID3D12CommandQueue_Release(queue);
    ok(!refcount, "ID3D12CommandQueue has %u references left.\n", (unsigned int)refcount);

    desc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;
    for (i = 0; i < ARRAY_SIZE(direct_queues); ++i)
    {
        hr = ID3D12Device_CreateCommandQueue(device, &desc, &IID_ID3D12CommandQueue, (void **)&direct_queues[i]);
        ok(hr == S_OK, "Failed to create direct command queue %u, hr %#x.\n", hr, i);
    }
    desc.Type = D3D12_COMMAND_LIST_TYPE_COMPUTE;
    for (i = 0; i < ARRAY_SIZE(compute_queues); ++i)
    {
        hr = ID3D12Device_CreateCommandQueue(device, &desc, &IID_ID3D12CommandQueue, (void **)&compute_queues[i]);
        ok(hr == S_OK, "Failed to create compute command queue %u, hr %#x.\n", hr, i);
    }

    for (i = 0; i < ARRAY_SIZE(direct_queues); ++i)
        ID3D12CommandQueue_Release(direct_queues[i]);
    for (i = 0; i < ARRAY_SIZE(compute_queues); ++i)
        ID3D12CommandQueue_Release(compute_queues[i]);

    refcount = ID3D12Device_Release(device);
    ok(!refcount, "ID3D12Device has %u references left.\n", (unsigned int)refcount);
}

void test_create_command_signature(void)
{
    D3D12_INDIRECT_ARGUMENT_DESC argument_desc[3];
    D3D12_COMMAND_SIGNATURE_DESC signature_desc;
    ID3D12CommandSignature *command_signature;
    ID3D12Device *device;
    unsigned int i;
    ULONG refcount;
    HRESULT hr;

    if (!(device = create_device()))
    {
        skip("Failed to create device.\n");
        return;
    }

    signature_desc.ByteStride = 1024;
    signature_desc.NumArgumentDescs = ARRAY_SIZE(argument_desc);
    signature_desc.pArgumentDescs = argument_desc;
    signature_desc.NodeMask = 0;

    for (i = 0; i < ARRAY_SIZE(argument_desc); ++i)
        argument_desc[i].Type = D3D12_INDIRECT_ARGUMENT_TYPE_DRAW;
    hr = ID3D12Device_CreateCommandSignature(device, &signature_desc,
            NULL, &IID_ID3D12CommandSignature, (void **)&command_signature);
    ok(hr == E_INVALIDARG, "Got unexpected hr %#x.\n", hr);

    for (i = 0; i < ARRAY_SIZE(argument_desc); ++i)
        argument_desc[i].Type = D3D12_INDIRECT_ARGUMENT_TYPE_DRAW_INDEXED;
    hr = ID3D12Device_CreateCommandSignature(device, &signature_desc,
            NULL, &IID_ID3D12CommandSignature, (void **)&command_signature);
    ok(hr == E_INVALIDARG, "Got unexpected hr %#x.\n", hr);

    for (i = 0; i < ARRAY_SIZE(argument_desc); ++i)
        argument_desc[i].Type = D3D12_INDIRECT_ARGUMENT_TYPE_DISPATCH;
    hr = ID3D12Device_CreateCommandSignature(device, &signature_desc,
            NULL, &IID_ID3D12CommandSignature, (void **)&command_signature);
    ok(hr == E_INVALIDARG, "Got unexpected hr %#x.\n", hr);

    argument_desc[0].Type = D3D12_INDIRECT_ARGUMENT_TYPE_DISPATCH;
    argument_desc[1].Type = D3D12_INDIRECT_ARGUMENT_TYPE_DRAW;
    signature_desc.NumArgumentDescs = 2;
    hr = ID3D12Device_CreateCommandSignature(device, &signature_desc,
            NULL, &IID_ID3D12CommandSignature, (void **)&command_signature);
    ok(hr == E_INVALIDARG, "Got unexpected hr %#x.\n", hr);

    refcount = ID3D12Device_Release(device);
    ok(!refcount, "ID3D12Device has %u references left.\n", (unsigned int)refcount);
}

struct private_data
{
    ID3D12Object *object;
    GUID guid;
    unsigned int value;
};

static void private_data_thread_main(void *untyped_data)
{
    struct private_data *data = untyped_data;
    unsigned int i;
    HRESULT hr;

    hr = ID3D12Object_SetPrivateData(data->object, &data->guid, sizeof(data->value), &data->value);
    ok(hr == S_OK, "Got unexpected hr %#x.\n", hr);

    for (i = 0; i < 100000; ++i)
    {
        hr = ID3D12Object_SetPrivateData(data->object, &data->guid, 0, NULL);
        ok(hr == S_OK, "Got unexpected hr %#x.\n", hr);
        hr = ID3D12Object_SetPrivateData(data->object, &data->guid, sizeof(data->value), &data->value);
        ok(hr == S_OK, "Got unexpected hr %#x.\n", hr);
    }
}

struct private_data_interface
{
    ID3D12Object *object;
    GUID guid;
    IUnknown *iface;
};

static void private_data_interface_thread_main(void *untyped_data)
{
    struct private_data_interface *data = untyped_data;
    unsigned int i;
    HRESULT hr;

    for (i = 0; i < 100000; ++i)
    {
        hr = ID3D12Object_SetPrivateDataInterface(data->object, &data->guid, data->iface);
        ok(hr == S_OK, "Got unexpected hr %#x.\n", hr);
        hr = ID3D12Object_SetPrivateDataInterface(data->object, &data->guid, NULL);
        ok(hr == S_OK, "Got unexpected hr %#x.\n", hr);
        hr = ID3D12Object_SetPrivateDataInterface(data->object, &data->guid, data->iface);
        ok(hr == S_OK, "Got unexpected hr %#x.\n", hr);
    }
}

void test_multithread_private_data(void)
{
    static const GUID guid = {0xfdb37466, 0x428f, 0x4edf, {0xa3, 0x7f, 0x9b, 0x1d, 0xf4, 0x88, 0xc5, 0x00}};
    struct private_data_interface private_data_interface[4];
    HANDLE private_data_interface_thread[4];
    struct private_data private_data[4];
    ID3D12RootSignature *root_signature;
    HANDLE private_data_thread[4];
    IUnknown *test_object, *unk;
    ID3D12Device *device;
    ID3D12Object *object;
    unsigned int value;
    unsigned int size;
    unsigned int id;
    unsigned int i;
    ULONG refcount;
    HRESULT hr;

    if (!(device = create_device()))
    {
        skip("Failed to create device.\n");
        return;
    }

    root_signature = create_empty_root_signature(device,
            D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT);
    hr = ID3D12RootSignature_QueryInterface(root_signature, &IID_ID3D12Object, (void **)&object);
    ok(hr == S_OK, "Got unexpected hr %#x.\n", hr);
    ID3D12RootSignature_Release(root_signature);

    hr = ID3D12Device_CreateFence(device, 0, D3D12_FENCE_FLAG_NONE,
            &IID_ID3D12Fence, (void **)&test_object);
    ok(hr == S_OK, "Failed to create fence, hr %#x.\n", hr);

    for (i = 0, id = 1; i < ARRAY_SIZE(private_data_interface); ++i, ++id)
    {
        private_data_interface[i].object = object;
        private_data_interface[i].guid = guid;
        private_data_interface[i].guid.Data4[7] = id;

        hr = ID3D12Device_CreateFence(device, 0, D3D12_FENCE_FLAG_NONE,
                &IID_ID3D12Fence, (void **)&private_data_interface[i].iface);
        ok(hr == S_OK, "Failed to create fence %u, hr %#x.\n", i, hr);
    }
    for (i = 0; i < ARRAY_SIZE(private_data); ++i, ++id)
    {
        private_data[i].object = object;
        private_data[i].guid = guid;
        private_data[i].guid.Data4[7] = id;
        private_data[i].value = id;
    }

    for (i = 0; i < 4; ++i)
    {
        private_data_interface_thread[i] = create_thread(private_data_interface_thread_main, &private_data_interface[i]);
        private_data_thread[i] = create_thread(private_data_thread_main, &private_data[i]);
    }

    for (i = 0; i < 100000; ++i)
    {
        hr = ID3D12Object_SetPrivateDataInterface(object, &guid, test_object);
        ok(hr == S_OK, "Got unexpected hr %#x.\n", hr);
        hr = ID3D12Object_SetPrivateDataInterface(object, &guid, NULL);
        ok(hr == S_OK, "Got unexpected hr %#x.\n", hr);
        hr = ID3D12Object_SetPrivateDataInterface(object, &guid, test_object);
        ok(hr == S_OK, "Got unexpected hr %#x.\n", hr);
    }

    for (i = 0; i < 4; ++i)
    {
        ok(join_thread(private_data_interface_thread[i]), "Failed to join thread %u.\n", i);
        ok(join_thread(private_data_thread[i]), "Failed to join thread %u.\n", i);
    }

    for (i = 0; i < ARRAY_SIZE(private_data_interface); ++i)
    {
        size = sizeof(unk);
        hr = ID3D12Object_GetPrivateData(object, &private_data_interface[i].guid, &size, &unk);
        ok(hr == S_OK, "Got unexpected hr %#x.\n", hr);

        ok(unk == private_data_interface[i].iface, "Got %p, expected %p.\n", unk, private_data_interface[i].iface);
        IUnknown_Release(unk);
        refcount = IUnknown_Release(private_data_interface[i].iface);
        ok(refcount == 1, "Got unexpected refcount %u.\n", (unsigned int)refcount);
    }
    for (i = 0; i < ARRAY_SIZE(private_data); ++i)
    {
        size = sizeof(value);
        hr = ID3D12Object_GetPrivateData(object, &private_data[i].guid, &size, &value);
        ok(hr == S_OK, "Got unexpected hr %#x.\n", hr);

        ok(value == private_data[i].value, "Got %u, expected %u.\n", value, private_data[i].value);
    }

    hr = ID3D12Object_SetPrivateDataInterface(object, &guid, NULL);
    ok(hr == S_OK, "Got unexpected hr %#x.\n", hr);
    refcount = IUnknown_Release(test_object);
    ok(!refcount, "Test object has %u references left.\n", (unsigned int)refcount);
    refcount = ID3D12Object_Release(object);
    ok(!refcount, "Object has %u references left.\n", (unsigned int)refcount);
    refcount = ID3D12Device_Release(device);
    ok(!refcount, "ID3D12Device has %u references left.\n", (unsigned int)refcount);
}

void test_reset_command_allocator(void)
{
    ID3D12CommandAllocator *command_allocator, *command_allocator2;
    ID3D12GraphicsCommandList *command_list, *command_list2;
    D3D12_COMMAND_QUEUE_DESC command_queue_desc;
    ID3D12CommandQueue *queue;
    ID3D12Device *device;
    unsigned int i;
    ULONG refcount;
    HRESULT hr;

    static const D3D12_COMMAND_LIST_TYPE tests[] =
    {
        D3D12_COMMAND_LIST_TYPE_DIRECT,
        D3D12_COMMAND_LIST_TYPE_BUNDLE,
    };

    if (!(device = create_device()))
    {
        skip("Failed to create device.\n");
        return;
    }

    for (i = 0; i < ARRAY_SIZE(tests); i++)
    {
        const D3D12_COMMAND_LIST_TYPE type = tests[i];

        hr = ID3D12Device_CreateCommandAllocator(device, type,
                &IID_ID3D12CommandAllocator, (void **)&command_allocator);
        ok(SUCCEEDED(hr), "Failed to create command allocator, hr %#x.\n", hr);

        hr = ID3D12CommandAllocator_Reset(command_allocator);
        ok(hr == S_OK, "Got unexpected hr %#x.\n", hr);
        hr = ID3D12CommandAllocator_Reset(command_allocator);
        ok(hr == S_OK, "Got unexpected hr %#x.\n", hr);

        hr = ID3D12Device_CreateCommandList(device, 0, type,
                command_allocator, NULL, &IID_ID3D12GraphicsCommandList, (void **)&command_list);
        ok(SUCCEEDED(hr), "Failed to create command list, hr %#x.\n", hr);

        hr = ID3D12CommandAllocator_Reset(command_allocator);
        ok(hr == E_FAIL, "Got unexpected hr %#x.\n", hr);
        hr = ID3D12CommandAllocator_Reset(command_allocator);
        ok(hr == E_FAIL, "Got unexpected hr %#x.\n", hr);

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

        hr = ID3D12CommandAllocator_Reset(command_allocator);
        ok(hr == S_OK, "Got unexpected hr %#x.\n", hr);
        hr = ID3D12CommandAllocator_Reset(command_allocator);
        ok(hr == S_OK, "Got unexpected hr %#x.\n", hr);

        hr = ID3D12GraphicsCommandList_Reset(command_list, command_allocator, NULL);
        ok(SUCCEEDED(hr), "Failed to reset command list, hr %#x.\n", hr);

        hr = ID3D12CommandAllocator_Reset(command_allocator);
        ok(hr == E_FAIL, "Got unexpected hr %#x.\n", hr);

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

        hr = ID3D12Device_CreateCommandAllocator(device, type,
                &IID_ID3D12CommandAllocator, (void **)&command_allocator2);
        ok(SUCCEEDED(hr), "Failed to create command allocator, hr %#x.\n", hr);

        if (type != D3D12_COMMAND_LIST_TYPE_BUNDLE)
        {
            command_queue_desc.Type = type;
            command_queue_desc.Priority = D3D12_COMMAND_QUEUE_PRIORITY_NORMAL;
            command_queue_desc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE;
            command_queue_desc.NodeMask = 0;
            hr = ID3D12Device_CreateCommandQueue(device, &command_queue_desc,
                    &IID_ID3D12CommandQueue, (void **)&queue);
            ok(SUCCEEDED(hr), "Failed to create command queue, hr %#x.\n", hr);

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

            /* A command list can be reset when it is in use. */
            hr = ID3D12GraphicsCommandList_Reset(command_list, command_allocator2, NULL);
            ok(SUCCEEDED(hr), "Failed to reset command list, hr %#x.\n", hr);
            hr = ID3D12GraphicsCommandList_Close(command_list);
            ok(SUCCEEDED(hr), "Failed to close command list, hr %#x.\n", hr);

            wait_queue_idle(device, queue);
            hr = ID3D12CommandAllocator_Reset(command_allocator);
            ok(hr == S_OK, "Got unexpected hr %#x.\n", hr);
            hr = ID3D12GraphicsCommandList_Reset(command_list, command_allocator, NULL);
            ok(SUCCEEDED(hr), "Failed to reset command list, hr %#x.\n", hr);

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

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

            wait_queue_idle(device, queue);

            hr = ID3D12CommandAllocator_Reset(command_allocator);
            ok(hr == S_OK, "Got unexpected hr %#x.\n", hr);
            hr = ID3D12GraphicsCommandList_Reset(command_list, command_allocator, NULL);
            ok(SUCCEEDED(hr), "Failed to reset command list, hr %#x.\n", hr);
            ID3D12CommandQueue_Release(queue);
        }

        /* A command allocator can be used with one command list at a time. */
        hr = ID3D12Device_CreateCommandList(device, 0, type,
                command_allocator, NULL, &IID_ID3D12GraphicsCommandList, (void **)&command_list2);
        ok(hr == E_INVALIDARG, "Got unexpected hr %#x.\n", hr);

        hr = ID3D12Device_CreateCommandList(device, 0, type,
                command_allocator2, NULL, &IID_ID3D12GraphicsCommandList, (void **)&command_list2);
        ok(hr == S_OK, "Failed to create command list, hr %#x.\n", hr);

        hr = ID3D12GraphicsCommandList_Close(command_list2);
        ok(SUCCEEDED(hr), "Failed to close command list, hr %#x.\n", hr);
        hr = ID3D12GraphicsCommandList_Reset(command_list2, command_allocator, NULL);
        ok(hr == E_INVALIDARG, "Got unexpected hr %#x.\n", hr);
        ID3D12GraphicsCommandList_Release(command_list2);

        /* A command allocator can be re-used after closing the command list. */
        hr = ID3D12Device_CreateCommandList(device, 0, type,
                command_allocator, NULL, &IID_ID3D12GraphicsCommandList, (void **)&command_list2);
        ok(hr == E_INVALIDARG, "Got unexpected hr %#x.\n", hr);
        hr = ID3D12GraphicsCommandList_Close(command_list);
        ok(SUCCEEDED(hr), "Failed to close command list, hr %#x.\n", hr);
        hr = ID3D12Device_CreateCommandList(device, 0, type,
                command_allocator, NULL, &IID_ID3D12GraphicsCommandList, (void **)&command_list2);
        ok(hr == S_OK, "Failed to create command list, hr %#x.\n", hr);

        ID3D12GraphicsCommandList_Release(command_list);
        ID3D12GraphicsCommandList_Release(command_list2);
        ID3D12CommandAllocator_Release(command_allocator);
        ID3D12CommandAllocator_Release(command_allocator2);
    }

    refcount = ID3D12Device_Release(device);
    ok(!refcount, "ID3D12Device has %u references left.\n", (unsigned int)refcount);
}

void test_object_interface_null_cases(void)
{
    ID3D12Device *device;
    ID3D12Fence *fence;
    HRESULT hr;
    UINT ref;

    if (!(device = create_device()))
    {
        skip("Failed to create device.\n");
        return;
    }

    hr = ID3D12Device_CreateFence(device, 0, D3D12_FENCE_FLAG_NONE, &IID_ID3D12Fence, (void **)&fence);
    ok(SUCCEEDED(hr), "Failed to create fence, hr #%x.\n", hr);

    hr = ID3D12Device_CreateFence(device, 0, D3D12_FENCE_FLAG_NONE, &IID_ID3D12Fence, NULL);
    ok(hr == S_FALSE, "Unexpected hr #%x.\n", hr);

    /* S_FALSE is returned even for bogus requested interfaces. */
    hr = ID3D12Device_CreateFence(device, 0, D3D12_FENCE_FLAG_NONE, &IID_ID3D12Heap, NULL);
    ok(hr == S_FALSE, "Unexpected hr #%x.\n", hr);

    /* E_POINTER is always returned if QueryInterface ppOutput is NULL. */
    hr = ID3D12Fence_QueryInterface(fence, &IID_IUnknown, NULL);
    ok(hr == E_POINTER, "Unexpected hr #%x.\n", hr);

    hr = ID3D12Fence_QueryInterface(fence, &IID_ID3D12Fence, NULL);
    ok(hr == E_POINTER, "Unexpected hr #%x.\n", hr);

    hr = ID3D12Fence_QueryInterface(fence, &IID_ID3D12Heap, NULL);
    ok(hr == E_POINTER, "Unexpected hr #%x.\n", hr);

    ID3D12Fence_Release(fence);
    ref = ID3D12Device_Release(device);
    ok(ref == 0, "Unexpected ref-count %u\n", ref);
}

void test_object_interface(void)
{
    D3D12_DESCRIPTOR_HEAP_DESC descriptor_heap_desc;
    D3D12_QUERY_HEAP_DESC query_heap_desc;
    ID3D12RootSignature *root_signature;
    ULONG refcount, expected_refcount;
    ID3D12CommandAllocator *allocator;
    D3D12_HEAP_DESC heap_desc;
    IUnknown *test_object;
    ID3D12Device *device;
    ID3D12Object *object;
    IUnknown *unknown;
    unsigned int size;
    unsigned int i;
    IUnknown *ptr;
    HRESULT hr;

    static const GUID test_guid
            = {0xfdb37466, 0x428f, 0x4edf, {0xa3, 0x7f, 0x9b, 0x1d, 0xf4, 0x88, 0xc5, 0xfc}};
    static const GUID test_guid2
            = {0x2e5afac2, 0x87b5, 0x4c10, {0x9b, 0x4b, 0x89, 0xd7, 0xd1, 0x12, 0xe7, 0x2b}};
    static const DWORD data[] = {1, 2, 3, 4};
    static const char terminated_name_a[] = { 'T', 'e', 's', 't', 'A', '\0' };
    static const char non_terminated_name_a[] = { 'T', 'e', 's', 't' };
    static const WCHAR non_terminated_name_w[] = { L'T', L'e', L's', L't', L'w' };
    WCHAR temp_name_buffer[1024];
    static const GUID *tests[] =
    {
        &IID_ID3D12CommandAllocator,
        &IID_ID3D12CommandList,
        &IID_ID3D12CommandQueue,
        &IID_ID3D12CommandSignature,
        &IID_ID3D12DescriptorHeap,
        &IID_ID3D12Device,
        &IID_ID3D12Fence,
        &IID_ID3D12Heap,
        &IID_ID3D12PipelineState,
        &IID_ID3D12QueryHeap,
        &IID_ID3D12Resource,
        &IID_ID3D12RootSignature,
    };

    if (!(device = create_device()))
    {
        skip("Failed to create device.\n");
        return;
    }

    for (i = 0; i < ARRAY_SIZE(tests); ++i)
    {
        if (IsEqualGUID(tests[i], &IID_ID3D12CommandAllocator))
        {
            vkd3d_test_set_context("command allocator");
            hr = ID3D12Device_CreateCommandAllocator(device, D3D12_COMMAND_LIST_TYPE_DIRECT,
                    &IID_IUnknown, (void **)&unknown);
            ok(hr == S_OK, "Failed to create command allocator, hr %#x.\n", hr);
        }
        else if (IsEqualGUID(tests[i], &IID_ID3D12CommandList))
        {
            vkd3d_test_set_context("command list");
            hr = ID3D12Device_CreateCommandAllocator(device, D3D12_COMMAND_LIST_TYPE_DIRECT,
                    &IID_ID3D12CommandAllocator, (void **)&allocator);
            ok(hr == S_OK, "Failed to create command allocator, hr %#x.\n", hr);
            hr = ID3D12Device_CreateCommandList(device, 0, D3D12_COMMAND_LIST_TYPE_DIRECT,
                    allocator, NULL, &IID_IUnknown, (void **)&unknown);
            ok(hr == S_OK, "Failed to create command list, hr %#x.\n", hr);
            ID3D12CommandAllocator_Release(allocator);
        }
        else if (IsEqualGUID(tests[i], &IID_ID3D12CommandQueue))
        {
            vkd3d_test_set_context("command queue");
            unknown = (IUnknown *)create_command_queue(device,
                    D3D12_COMMAND_LIST_TYPE_DIRECT, D3D12_COMMAND_QUEUE_PRIORITY_NORMAL);
        }
        else if (IsEqualGUID(tests[i], &IID_ID3D12CommandSignature))
        {
            vkd3d_test_set_context("command signature");
            unknown = (IUnknown *)create_command_signature(device, D3D12_INDIRECT_ARGUMENT_TYPE_DRAW);
        }
        else if (IsEqualGUID(tests[i], &IID_ID3D12DescriptorHeap))
        {
            vkd3d_test_set_context("descriptor heap");
            descriptor_heap_desc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV;
            descriptor_heap_desc.NumDescriptors = 16;
            descriptor_heap_desc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
            descriptor_heap_desc.NodeMask = 0;
            hr = ID3D12Device_CreateDescriptorHeap(device, &descriptor_heap_desc,
                    &IID_ID3D12DescriptorHeap, (void **)&unknown);
            ok(hr == S_OK, "Failed to create descriptor heap, hr %#x.\n", hr);
        }
        else if (IsEqualGUID(tests[i], &IID_ID3D12Device))
        {
            vkd3d_test_set_context("device");
            unknown = (IUnknown *)create_device();
        }
        else if (IsEqualGUID(tests[i], &IID_ID3D12Fence))
        {
            vkd3d_test_set_context("fence");
            hr = ID3D12Device_CreateFence(device, 0, D3D12_FENCE_FLAG_NONE,
                    &IID_IUnknown, (void **)&unknown);
            ok(hr == S_OK, "Failed to create fence, hr %#x.\n", hr);
        }
        else if (IsEqualGUID(tests[i], &IID_ID3D12Heap))
        {
            vkd3d_test_set_context("heap");
            heap_desc.SizeInBytes = D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT;
            memset(&heap_desc.Properties, 0, sizeof(heap_desc.Properties));
            heap_desc.Properties.Type = D3D12_HEAP_TYPE_DEFAULT;
            heap_desc.Alignment = 0;
            heap_desc.Flags = D3D12_HEAP_FLAG_ALLOW_ONLY_NON_RT_DS_TEXTURES;
            hr = ID3D12Device_CreateHeap(device, &heap_desc, &IID_ID3D12Heap, (void **)&unknown);
            ok(hr == S_OK, "Failed to create heap, hr %#x.\n", hr);
        }
        else if (IsEqualGUID(tests[i], &IID_ID3D12PipelineState))
        {
            vkd3d_test_set_context("pipeline state");
            root_signature = create_empty_root_signature(device, 0);
            unknown = (IUnknown *)create_pipeline_state(device,
                    root_signature, DXGI_FORMAT_R8G8B8A8_UNORM, NULL, NULL, NULL);
            ID3D12RootSignature_Release(root_signature);
        }
        else if (IsEqualGUID(tests[i], &IID_ID3D12QueryHeap))
        {
            vkd3d_test_set_context("query heap");
            query_heap_desc.Type = D3D12_QUERY_HEAP_TYPE_OCCLUSION;
            query_heap_desc.Count = 8;
            query_heap_desc.NodeMask = 0;
            hr = ID3D12Device_CreateQueryHeap(device, &query_heap_desc,
                    &IID_ID3D12QueryHeap, (void **)&unknown);
            ok(hr == S_OK, "Failed to create query heap, hr %#x.\n", hr);
        }
        else if (IsEqualGUID(tests[i], &IID_ID3D12Resource))
        {
            vkd3d_test_set_context("resource");
            unknown = (IUnknown *)create_readback_buffer(device, 512);
        }
        else if (IsEqualGUID(tests[i], &IID_ID3D12RootSignature))
        {
            vkd3d_test_set_context("root signature");
            unknown = (IUnknown *)create_empty_root_signature(device, 0);
        }
        else
        {
            unknown = NULL;
        }

        ok(unknown, "Unhandled object type %u.\n", i);
        object = NULL;
        hr = IUnknown_QueryInterface(unknown, &IID_ID3D12Object, (void **)&object);
        ok(hr == S_OK, "Got unexpected hr %#x.\n", hr);
        IUnknown_Release(unknown);

        hr = ID3D12Object_SetPrivateData(object, &test_guid, 0, NULL);
        ok(hr == S_FALSE, "Got unexpected hr %#x.\n", hr);
        hr = ID3D12Object_SetPrivateDataInterface(object, &test_guid, NULL);
        ok(hr == S_OK, "Got unexpected hr %#x.\n", hr);
        hr = ID3D12Object_SetPrivateData(object, &test_guid, ~0u, NULL);
        ok(hr == S_OK, "Got unexpected hr %#x.\n", hr);
        hr = ID3D12Object_SetPrivateData(object, &test_guid, ~0u, NULL);
        ok(hr == S_FALSE, "Got unexpected hr %#x.\n", hr);

        hr = ID3D12Object_SetPrivateDataInterface(object, &test_guid, NULL);
        ok(hr == S_OK, "Got unexpected hr %#x.\n", hr);
        size = sizeof(ptr) * 2;
        ptr = (IUnknown *)(uintptr_t)0xdeadbeef;
        hr = ID3D12Object_GetPrivateData(object, &test_guid, &size, &ptr);
        ok(hr == S_OK, "Got unexpected hr %#x.\n", hr);
        ok(!ptr, "Got unexpected pointer %p.\n", ptr);
        ok(size == sizeof(IUnknown *), "Got unexpected size %u.\n", size);

        hr = ID3D12Device_CreateFence(device, 0, D3D12_FENCE_FLAG_NONE,
                &IID_ID3D12Fence, (void **)&test_object);
        ok(hr == S_OK, "Failed to create fence, hr %#x.\n", hr);

        refcount = get_refcount(test_object);
        hr = ID3D12Object_SetPrivateDataInterface(object, &test_guid, (IUnknown *)test_object);
        ok(hr == S_OK, "Got unexpected hr %#x.\n", hr);
        expected_refcount = refcount + 1;
        refcount = get_refcount(test_object);
        ok(refcount == expected_refcount, "Got unexpected refcount %u, expected %u.\n",
                (unsigned int)refcount, (unsigned int)expected_refcount);
        hr = ID3D12Object_SetPrivateDataInterface(object, &test_guid, (IUnknown *)test_object);
        ok(hr == S_OK, "Got unexpected hr %#x.\n", hr);
        refcount = get_refcount(test_object);
        ok(refcount == expected_refcount, "Got unexpected refcount %u, expected %u.\n",
                (unsigned int)refcount, (unsigned int)expected_refcount);

        hr = ID3D12Object_SetPrivateDataInterface(object, &test_guid, NULL);
        ok(hr == S_OK, "Got unexpected hr %#x.\n", hr);
        --expected_refcount;
        refcount = get_refcount(test_object);
        ok(refcount == expected_refcount, "Got unexpected refcount %u, expected %u.\n",
                (unsigned int)refcount, (unsigned int)expected_refcount);

        hr = ID3D12Object_SetPrivateDataInterface(object, &test_guid, (IUnknown *)test_object);
        ok(hr == S_OK, "Got unexpected hr %#x.\n", hr);
        size = sizeof(data);
        hr = ID3D12Object_SetPrivateData(object, &test_guid, size, data);
        ok(hr == S_OK, "Got unexpected hr %#x.\n", hr);
        refcount = get_refcount(test_object);
        ok(refcount == expected_refcount, "Got unexpected refcount %u, expected %u.\n",
                (unsigned int)refcount, (unsigned int)expected_refcount);
        hr = ID3D12Object_SetPrivateData(object, &test_guid, 42, NULL);
        ok(hr == S_OK, "Got unexpected hr %#x.\n", hr);
        hr = ID3D12Object_SetPrivateData(object, &test_guid, 42, NULL);
        ok(hr == S_FALSE, "Got unexpected hr %#x.\n", hr);

        hr = ID3D12Object_SetPrivateDataInterface(object, &test_guid, (IUnknown *)test_object);
        ok(hr == S_OK, "Got unexpected hr %#x.\n", hr);
        ++expected_refcount;
        size = 2 * sizeof(ptr);
        ptr = NULL;
        hr = ID3D12Object_GetPrivateData(object, &test_guid, &size, &ptr);
        ok(hr == S_OK, "Got unexpected hr %#x.\n", hr);
        ok(size == sizeof(test_object), "Got unexpected size %u.\n", size);
        ++expected_refcount;
        refcount = get_refcount(test_object);
        ok(refcount == expected_refcount, "Got unexpected refcount %u, expected %u.\n",
                (unsigned int)refcount, (unsigned int)expected_refcount);
        IUnknown_Release(ptr);
        --expected_refcount;

        ptr = (IUnknown *)(uintptr_t)0xdeadbeef;
        size = 1;
        hr = ID3D12Object_GetPrivateData(object, &test_guid, &size, NULL);
        ok(hr == S_OK, "Got unexpected hr %#x.\n", hr);
        ok(size == sizeof(ptr), "Got unexpected size %u.\n", size);
        size = 2 * sizeof(ptr);
        hr = ID3D12Object_GetPrivateData(object, &test_guid, &size, NULL);
        ok(hr == S_OK, "Got unexpected hr %#x.\n", hr);
        ok(size == sizeof(ptr), "Got unexpected size %u.\n", size);
        refcount = get_refcount(test_object);
        ok(refcount == expected_refcount, "Got unexpected refcount %u, expected %u.\n",
                (unsigned int)refcount, (unsigned int)expected_refcount);

        size = 1;
        hr = ID3D12Object_GetPrivateData(object, &test_guid, &size, &ptr);
        ok(hr == DXGI_ERROR_MORE_DATA, "Got unexpected hr %#x.\n", hr);
        ok(size == sizeof(object), "Got unexpected size %u.\n", size);
        ok(ptr == (IUnknown *)(uintptr_t)0xdeadbeef, "Got unexpected pointer %p.\n", ptr);
        size = 1;
        hr = ID3D12Object_GetPrivateData(object, &test_guid2, &size, &ptr);
        ok(hr == DXGI_ERROR_NOT_FOUND, "Got unexpected hr %#x.\n", hr);
        ok(!size, "Got unexpected size %u.\n", size);
        ok(ptr == (IUnknown *)(uintptr_t)0xdeadbeef, "Got unexpected pointer %p.\n", ptr);

        if (IsEqualGUID(tests[i], &IID_ID3D12Device))
        {
            hr = ID3D12Object_SetPrivateDataInterface(object, &test_guid, NULL);
            ok(hr == S_OK, "Got unexpected hr %#x.\n", hr);
        }

        hr = ID3D12Object_SetName(object, u"");
        ok(hr == S_OK, "Got unexpected hr %#x.\n", hr);

        hr = ID3D12Object_SetName(object, u"deadbeef");
        ok(hr == S_OK, "Got unexpected hr %#x.\n", hr);

        size = 1;
        hr = ID3D12Object_GetPrivateData(object, &WKPDID_D3DDebugObjectNameW, &size, NULL);
        ok(hr == S_OK, "Got unexpected hr %#x.\n", hr);
        ok(size == 18, "Got unexpected size %u.\n", size);
        hr = ID3D12Object_GetPrivateData(object, &WKPDID_D3DDebugObjectNameW, &size, temp_name_buffer);
        ok(hr == S_OK, "Got unexpected hr %#x.\n", hr);
        ok(size == 18, "Got unexpected size %u.\n", size);
        ok(temp_name_buffer[1] == L'e', "Got unexpected name");
        size = 1;
        hr = ID3D12Object_GetPrivateData(object, &WKPDID_D3DDebugObjectName, &size, NULL);
        ok(hr == DXGI_ERROR_NOT_FOUND, "Got unexpected hr %#x.\n", hr);

#if 0
        /* NULL name crashes on Windows 11 22621. */
        hr = ID3D12Object_SetName(object, NULL);
        ok(hr == S_OK, "Got unexpected hr %#x.\n", hr);

        hr = ID3D12Object_GetPrivateData(object, &WKPDID_D3DDebugObjectNameW, &size, NULL);
        ok(hr == DXGI_ERROR_NOT_FOUND, "Got unexpected hr %#x.\n", hr);
#endif

        hr = ID3D12Object_SetPrivateData(object, &WKPDID_D3DDebugObjectName, sizeof(terminated_name_a), terminated_name_a);
        ok(hr == S_OK, "Got unexpected hr %#x.\n", hr);

        hr = ID3D12Object_SetPrivateData(object, &WKPDID_D3DDebugObjectName, sizeof(non_terminated_name_a), non_terminated_name_a);
        ok(hr == S_OK, "Got unexpected hr %#x.\n", hr);

        hr = ID3D12Object_SetPrivateData(object, &WKPDID_D3DDebugObjectNameW, sizeof(non_terminated_name_w), non_terminated_name_w);
        ok(hr == S_OK, "Got unexpected hr %#x.\n", hr);

        hr = ID3D12Object_SetPrivateData(object, &WKPDID_D3DDebugObjectNameW, 0, NULL);
        ok(hr == S_OK, "Got unexpected hr %#x.\n", hr);

        hr = ID3D12Object_SetPrivateData(object, &WKPDID_D3DDebugObjectName, 0, NULL);
        ok(hr == S_OK, "Got unexpected hr %#x.\n", hr);

        ID3D12Object_Release(object);

        refcount = IUnknown_Release(test_object);
        ok(!refcount, "Test object has %u references left.\n", (unsigned int)refcount);

        vkd3d_test_set_context(NULL);
    }

    refcount = ID3D12Device_Release(device);
    ok(!refcount, "ID3D12Device has %u references left.\n", (unsigned int)refcount);
}

void test_device_removed_reason(void)
{
    D3D12_COMMAND_QUEUE_DESC command_queue_desc;
    ID3D12CommandAllocator *command_allocator;
    ID3D12GraphicsCommandList *command_list;
    ID3D12CommandQueue *queue, *tmp_queue;
    ID3D12Device *device;
    ULONG refcount;
    HRESULT hr;

    if (!(device = create_device()))
    {
        skip("Failed to create device.\n");
        return;
    }

    hr = ID3D12Device_GetDeviceRemovedReason(device);
    ok(hr == S_OK, "Got unexpected hr %#x.\n", hr);

    command_queue_desc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;
    command_queue_desc.Priority = D3D12_COMMAND_QUEUE_PRIORITY_NORMAL;
    command_queue_desc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE;
    command_queue_desc.NodeMask = 0;
    hr = ID3D12Device_CreateCommandQueue(device, &command_queue_desc,
            &IID_ID3D12CommandQueue, (void **)&queue);
    ok(SUCCEEDED(hr), "Failed to create command queue, hr %#x.\n", hr);

    hr = ID3D12Device_CreateCommandAllocator(device, D3D12_COMMAND_LIST_TYPE_DIRECT,
            &IID_ID3D12CommandAllocator, (void **)&command_allocator);
    ok(SUCCEEDED(hr), "Failed to create command allocator, hr %#x.\n", hr);

    hr = ID3D12Device_CreateCommandList(device, 0, D3D12_COMMAND_LIST_TYPE_DIRECT,
            command_allocator, NULL, &IID_ID3D12GraphicsCommandList, (void **)&command_list);
    ok(SUCCEEDED(hr), "Failed to create command list, hr %#x.\n", hr);

    /* Execute a command list in the recording state. */
    exec_command_list(queue, command_list);

    hr = ID3D12Device_GetDeviceRemovedReason(device);
    ok(hr == DXGI_ERROR_INVALID_CALL, "Got unexpected hr %#x.\n", hr);

    hr = ID3D12Device_CreateCommandQueue(device, &command_queue_desc,
            &IID_ID3D12CommandQueue, (void **)&tmp_queue);
    todo ok(hr == DXGI_ERROR_DEVICE_REMOVED, "Got unexpected hr %#x.\n", hr);
    if (SUCCEEDED(hr))
        ID3D12CommandQueue_Release(tmp_queue);

    hr = ID3D12Device_GetDeviceRemovedReason(device);
    ok(hr == DXGI_ERROR_INVALID_CALL, "Got unexpected hr %#x.\n", hr);

    ID3D12GraphicsCommandList_Release(command_list);
    ID3D12CommandAllocator_Release(command_allocator);
    ID3D12CommandQueue_Release(queue);
    refcount = ID3D12Device_Release(device);
    ok(!refcount, "ID3D12Device has %u references left.\n", (unsigned int)refcount);
}

void test_enumerate_meta_commands(void)
{
    D3D12_META_COMMAND_DESC dummy_desc, *descs;
    UINT desc_count, supported_count;
    ID3D12Device5 *device5;
    ID3D12Device *device;
    ULONG refcount;
    HRESULT hr;

    if (!(device = create_device()))
    {
        skip("Failed to create device.\n");
        return;
    }

    hr = ID3D12Device_QueryInterface(device, &IID_ID3D12Device5, (void**)&device5);
    ID3D12Device_Release(device);

    if (FAILED(hr))
    {
        skip("ID3D12Device5 not supported by implementation.\n");
        return;
    }

    hr = ID3D12Device5_EnumerateMetaCommands(device5, NULL, NULL);
    ok(hr == E_INVALIDARG, "Unexpected hr %#x.\n", hr);
    hr = ID3D12Device5_EnumerateMetaCommands(device5, NULL, &dummy_desc);
    ok(hr == E_INVALIDARG, "Unexpected hr %#x.\n", hr);

    desc_count = 0xdeadbeef;
    hr = ID3D12Device5_EnumerateMetaCommands(device5, &desc_count, NULL);
    ok(hr == S_OK, "Unexpected hr %#x.\n", hr);

    supported_count = desc_count;

    descs = calloc(supported_count, sizeof(*descs));
    memset(&dummy_desc, 0, sizeof(dummy_desc));

    hr = ID3D12Device5_EnumerateMetaCommands(device5, &desc_count, descs);
    ok(hr == S_OK, "Unexpected hr %#x.\n", hr);
    ok(desc_count == supported_count, "Unexpected desc count %u.\n", desc_count);

    if (desc_count)
    {
        desc_count -= 1;

        hr = ID3D12Device5_EnumerateMetaCommands(device5, &desc_count, descs);
        ok(hr == S_OK, "Unexpected hr %#x.\n", hr);
        ok(desc_count == supported_count, "Unexpected desc count %u.\n", desc_count);
    }

    /* The D3D12 runtime will crash or access random data if given a larger
     * count than what is supported by the device, so don't test that here. */

    free(descs);

    refcount = ID3D12Device5_Release(device5);
    ok(!refcount, "ID3D12Device has %u references left.\n", (unsigned int)refcount);
}

void test_vtable_origins(void)
{
#ifdef _WIN32
    HMODULE d3d12core_module, vtable_module;
    MEMORY_BASIC_INFORMATION info;
    ID3D12Device *device;
    SIZE_T ret;

    if (!(device = create_device()))
    {
        skip("Failed to create device.\n");
        return;
    }

    /* Skip this test if running on an older version of
     * Windows from before the D3D12 + D3D12Core split. */
    if (!(d3d12core_module = GetModuleHandleA("d3d12core")))
    {
        skip("No D3D12Core module, skipping.\n");
        ID3D12Device_Release(device);
        return;
    }

    ret = VirtualQuery(device->lpVtbl, &info, sizeof(info));
    ok(ret, "VirtualQuery of ID3D12Device VTable failed.\n");
    if (!ret)
    {
        skip("VirtualQuery failed, skipping vtable test.\n");
        ID3D12Device_Release(device);
        return;
    }

    vtable_module = (HMODULE)info.AllocationBase;

    /* Ensure the vtable for ID3D12Device comes from D3D12Core.dll */
    ok(vtable_module == d3d12core_module, "VTable for ID3D12Device not provided by D3D12Core.\n");

    ID3D12Device_Release(device);
#endif
}

static void __stdcall destruction_notifier_callback(void* userdata)
{
    bool *invoked = userdata;

    if (userdata)
        *invoked = true;
}

void test_destruction_notifier_callback(void)
{
    D3D12_HEAP_PROPERTIES heap_properties;
    ID3DDestructionNotifier *notifier;
    D3D12_RESOURCE_DESC resource_desc;
    ID3D12Resource *resource;
    ID3D12Device *device;
    UINT callback_id;
    unsigned int i;
    HRESULT hr;

    struct
    {
        bool expected;
        bool invoked;
    }
    tests[] =
    {
        { false }, { true }, { true }, { true },
    };

    if (!(device = create_device()))
    {
        skip("Failed to create device.\n");
        return;
    }

    memset(&heap_properties, 0, sizeof(heap_properties));
    heap_properties.Type = D3D12_HEAP_TYPE_DEFAULT;

    memset(&resource_desc, 0, sizeof(resource_desc));
    resource_desc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER;
    resource_desc.Alignment = D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT;
    resource_desc.Width = D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT;
    resource_desc.Height = 1;
    resource_desc.DepthOrArraySize = 1;
    resource_desc.MipLevels = 1;
    resource_desc.SampleDesc.Count = 1;
    resource_desc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR;

    hr = ID3D12Device_CreateCommittedResource(device, &heap_properties, D3D12_HEAP_FLAG_NONE,
            &resource_desc, D3D12_RESOURCE_STATE_COPY_DEST, NULL, &IID_ID3D12Resource, (void**)&resource);
    ok(hr == S_OK, "Failed to create resource, hr %#x.\n", hr);

    hr = ID3D12Resource_QueryInterface(resource, &IID_ID3DDestructionNotifier, (void**)&notifier);
    ok(hr == S_OK, "Failed to query destruction notifier from resource, hr %#x.\n", hr);

    if (FAILED(hr))
    {
        skip("ID3DDestructionNotifier not supported by implementation.\n");
        ID3D12Resource_Release(resource);
        ID3D12Device_Release(device);
    }

    callback_id = 0xdeadbeef;

    hr = ID3DDestructionNotifier_RegisterDestructionCallback(notifier, NULL, NULL, &callback_id);
    ok(hr == DXGI_ERROR_INVALID_CALL, "Got hr %#x, expected DXGI_ERROR_INVALID_CALL.\n", hr);
    ok(callback_id == 0xdeadbeef, "Got callback ID %d\n", callback_id);

    hr = ID3DDestructionNotifier_RegisterDestructionCallback(notifier, &destruction_notifier_callback, &tests[0].invoked, &callback_id);
    ok(hr == S_OK, "Failed to register callback, hr %#x.\n", hr);
    ok(callback_id == 1, "Expected callback ID 0, got %d.\n", callback_id);

    /* Not capturing the callback ID is fine */
    hr = ID3DDestructionNotifier_RegisterDestructionCallback(notifier, &destruction_notifier_callback, &tests[1].invoked, NULL);
    ok(hr == S_OK, "Failed to register callback, hr %#x.\n", hr);

    hr = ID3DDestructionNotifier_UnregisterDestructionCallback(notifier, callback_id);
    ok(hr == S_OK, "Failed to unregister callback, hr %#x.\n", hr);

    hr = ID3DDestructionNotifier_RegisterDestructionCallback(notifier, &destruction_notifier_callback, &tests[2].invoked, &callback_id);
    ok(hr == S_OK, "Failed to register callback, hr %#x.\n", hr);
    ok(callback_id == 2, "Expected callback ID 0, got %d.\n", callback_id);

    hr = ID3DDestructionNotifier_RegisterDestructionCallback(notifier, &destruction_notifier_callback, &tests[3].invoked, &callback_id);
    ok(hr == S_OK, "Failed to register callback, hr %#x.\n", hr);
    ok(callback_id == 3, "Expected callback ID 2, got %d.\n", callback_id);

    hr = ID3DDestructionNotifier_UnregisterDestructionCallback(notifier, 0);
    ok(hr == DXGI_ERROR_NOT_FOUND, "Got hr %#x, expected DXGI_ERROR_NOT_FOUND.\n", hr);
    hr = ID3DDestructionNotifier_UnregisterDestructionCallback(notifier, 0xdeadbeef);
    ok(hr == DXGI_ERROR_NOT_FOUND, "Got hr %#x, expected DXGI_ERROR_NOT_FOUND.\n", hr);

    ID3DDestructionNotifier_Release(notifier);
    ID3D12Resource_Release(resource);

    for (i = 0; i < ARRAY_SIZE(tests); i++)
    {
        vkd3d_test_set_context("Test %u", i);

        ok(tests[i].invoked == tests[i].expected, "Got %d, expected %d.\n",
                tests[i].invoked, tests[i].expected);
    }

    ID3D12Device_Release(device);
}

void test_destruction_notifier_interfaces(void)
{
    D3D12_COMMAND_SIGNATURE_DESC command_signature_desc;
    D3D12_INDIRECT_ARGUMENT_DESC command_signature_arg;
    D3D12_GRAPHICS_PIPELINE_STATE_DESC pipeline_desc;
    D3D12_DESCRIPTOR_HEAP_DESC descriptor_heap_desc;
    D3D12_ROOT_SIGNATURE_DESC root_signature_desc;
    ID3D12CommandAllocator *command_allocator;
    ID3D12CommandSignature *command_signature;
    ID3D12PipelineLibrary *pipeline_library;
    D3D12_QUERY_HEAP_DESC query_heap_desc;
    ID3D12DescriptorHeap *descriptor_heap;
    D3D12_COMMAND_QUEUE_DESC queue_desc;
    ID3D12RootSignature *root_signature;
    ID3DDestructionNotifier *notifier;
    D3D12_RESOURCE_DESC resource_desc;
    ID3D12CommandQueue *command_queue;
    ULONG refcount, expected_refcount;
    ID3D12CommandList *command_list;
    ID3D12PipelineState *pipeline;
    ID3D12QueryHeap *query_heap;
    D3D12_HEAP_DESC heap_desc;
    ID3D12Resource *resource;
    ID3D12Device1 *device1;
    ID3D12Device *device;
    ID3D12Fence *fence;
    ID3D12Heap *heap;
    unsigned int i;
    HRESULT hr;

    IUnknown** const tests[] =
    {
        (IUnknown**)&command_allocator,
        (IUnknown**)&command_list,
        (IUnknown**)&command_queue,
        (IUnknown**)&command_signature,
        (IUnknown**)&descriptor_heap,
        (IUnknown**)&device,
        (IUnknown**)&fence,
        (IUnknown**)&heap,
        (IUnknown**)&pipeline,
        (IUnknown**)&pipeline_library,
        (IUnknown**)&query_heap,
        (IUnknown**)&resource,
        (IUnknown**)&root_signature,
    };

    if (!(device = create_device()))
    {
        skip("Failed to create device.\n");
        return;
    }

    device1 = NULL;
    ID3D12Device_QueryInterface(device, &IID_ID3D12Device1, (void**)&device1);

    memset(&heap_desc, 0, sizeof(heap_desc));
    heap_desc.SizeInBytes = D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT;
    heap_desc.Alignment = D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT;
    heap_desc.Properties.Type = D3D12_HEAP_TYPE_DEFAULT;
    heap_desc.Flags = D3D12_HEAP_FLAG_ALLOW_ONLY_BUFFERS;

    hr = ID3D12Device_CreateHeap(device, &heap_desc, &IID_ID3D12Heap, (void**)&heap);
    ok(hr == S_OK, "Failed to create heap, hr %#x.\n", hr);

    memset(&resource_desc, 0, sizeof(resource_desc));
    resource_desc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER;
    resource_desc.Alignment = D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT;
    resource_desc.Width = D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT;
    resource_desc.Height = 1;
    resource_desc.DepthOrArraySize = 1;
    resource_desc.MipLevels = 1;
    resource_desc.SampleDesc.Count = 1;
    resource_desc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR;

    hr = ID3D12Device_CreatePlacedResource(device, heap, 0, &resource_desc,
            D3D12_RESOURCE_STATE_COPY_DEST, NULL, &IID_ID3D12Resource, (void**)&resource);
    ok(hr == S_OK, "Failed to create resource, hr %#x.\n", hr);

    hr = ID3D12Device_CreateFence(device, 0, D3D12_FENCE_FLAG_NONE,
            &IID_ID3D12Fence, (void**)&fence);
    ok(hr == S_OK, "Failed to create fence, hr %#x.\n", hr);

    memset(&root_signature_desc, 0, sizeof(root_signature_desc));
    root_signature_desc.Flags = D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT;

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

    memset(&query_heap_desc, 0, sizeof(query_heap_desc));
    query_heap_desc.Type = D3D12_QUERY_HEAP_TYPE_OCCLUSION;
    query_heap_desc.Count = 1024;

    hr = ID3D12Device_CreateQueryHeap(device, &query_heap_desc,
            &IID_ID3D12QueryHeap, (void**)&query_heap);
    ok(hr == S_OK, "Failed to create query heap, hr %#x.\n", hr);

    hr = ID3D12Device_CreateCommandAllocator(device, D3D12_COMMAND_LIST_TYPE_DIRECT,
            &IID_ID3D12CommandAllocator, (void**)&command_allocator);
    ok(hr == S_OK, "Failed to create command allocator, hr %#x.\n", hr);

    hr = ID3D12Device_CreateCommandList(device, 0, D3D12_COMMAND_LIST_TYPE_DIRECT,
            command_allocator, NULL, &IID_ID3D12CommandList, (void**)&command_list);
    ok(hr == S_OK, "Failed to create command list, hr %#x.\n", hr);

    memset(&queue_desc, 0, sizeof(queue_desc));
    queue_desc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;
    queue_desc.Priority = D3D12_COMMAND_QUEUE_PRIORITY_NORMAL;

    hr = ID3D12Device_CreateCommandQueue(device, &queue_desc, &IID_ID3D12CommandQueue, (void**)&command_queue);
    ok(hr == S_OK, "Failed to create command queue, hr %#x.\n", hr);

    memset(&descriptor_heap_desc, 0, sizeof(descriptor_heap_desc));
    descriptor_heap_desc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV;
    descriptor_heap_desc.NumDescriptors = 1024;
    descriptor_heap_desc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;

    hr = ID3D12Device_CreateDescriptorHeap(device, &descriptor_heap_desc, &IID_ID3D12DescriptorHeap, (void**)&descriptor_heap);
    ok(hr == S_OK, "Failed to create command queue, hr %#x.\n", hr);

    pipeline_library = NULL;

    if (device1)
    {
        hr = ID3D12Device1_CreatePipelineLibrary(device1, NULL, 0, &IID_ID3D12PipelineLibrary, (void**)&pipeline_library);
        ok(hr == S_OK, "Failed to create pipeline library, hr %#x.\n", hr);
    }

    memset(&command_signature_arg, 0, sizeof(command_signature_arg));
    command_signature_arg.Type = D3D12_INDIRECT_ARGUMENT_TYPE_DRAW;

    memset(&command_signature_desc, 0, sizeof(command_signature_desc));
    command_signature_desc.NumArgumentDescs = 1;
    command_signature_desc.pArgumentDescs = &command_signature_arg;
    command_signature_desc.ByteStride = 16;

    hr = ID3D12Device_CreateCommandSignature(device, &command_signature_desc,
            NULL, &IID_ID3D12CommandSignature, (void**)&command_signature);
    ok(hr == S_OK, "Failed to create command signature, hr %#x.\n", hr);

    init_pipeline_state_desc(&pipeline_desc, root_signature,
            DXGI_FORMAT_R8G8B8A8_UNORM, NULL, NULL, NULL);
    hr = ID3D12Device_CreateGraphicsPipelineState(device, &pipeline_desc, &IID_ID3D12PipelineState, (void**)&pipeline);
    ok(hr == S_OK, "Failed to create pipeline, hr %#x.\n", hr);

    for (i = 0; i < ARRAY_SIZE(tests); i++)
    {
        IUnknown* iface = *tests[i];

        vkd3d_test_set_context("Test %u", i);

        if (!iface)
        {
            skip("Interface not available.\n");
            continue;
        }

        expected_refcount = get_refcount(iface) + 1u;

        notifier = NULL;
        hr = IUnknown_QueryInterface(iface, &IID_ID3DDestructionNotifier, (void**)&notifier);
        ok(hr == S_OK, "Failed to query ID3DDestructionNotifier, hr %#x.\n", hr);

        if (!notifier)
            continue;

        expected_refcount++;
        ID3DDestructionNotifier_AddRef(notifier);

        refcount = get_refcount(iface);
        ok(refcount == expected_refcount, "Expected object ref count to be %u, got %u.\n", expected_refcount, refcount);

        expected_refcount--;
        refcount = ID3DDestructionNotifier_Release(notifier);
        ok(refcount == expected_refcount, "Expected notifier ref count to be %u, got %u.\n", expected_refcount, refcount);

        refcount = get_refcount(iface);
        ok(refcount == expected_refcount, "Expected object ref count to be %u, got %u.\n", expected_refcount, refcount);

        expected_refcount--;
        ID3DDestructionNotifier_Release(notifier);

        refcount = get_refcount(iface);
        ok(refcount == expected_refcount, "Expected object ref count to be %u, got %u.\n", expected_refcount, refcount);
    }

    ID3D12PipelineState_Release(pipeline);
    ID3D12CommandSignature_Release(command_signature);

    if (pipeline_library)
        ID3D12PipelineLibrary_Release(pipeline_library);

    ID3D12DescriptorHeap_Release(descriptor_heap);
    ID3D12CommandQueue_Release(command_queue);
    ID3D12CommandList_Release(command_list);
    ID3D12CommandAllocator_Release(command_allocator);
    ID3D12QueryHeap_Release(query_heap);
    ID3D12RootSignature_Release(root_signature);
    ID3D12Fence_Release(fence);
    ID3D12Resource_Release(resource);
    ID3D12Heap_Release(heap);

    if (device1)
        ID3D12Device1_Release(device1);

    ID3D12Device_Release(device);
}
