/*
 * This file is part of mpv.
 *
 * mpv 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.
 *
 * mpv 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 mpv.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <windows.h>
#include <d3d11.h>
#include <dxgi1_6.h>
#include <versionhelpers.h>

#include "common/common.h"
#include "common/msg.h"
#include "misc/bstr.h"
#include "osdep/io.h"
#include "osdep/threads.h"
#include "osdep/windows_utils.h"

#include "d3d11_helpers.h"

// Windows 8 enum value, not present in mingw-w64 headers
#define DXGI_ADAPTER_FLAG_SOFTWARE (2)
typedef HRESULT(WINAPI *PFN_CREATE_DXGI_FACTORY)(REFIID riid, void **ppFactory);

static mp_once d3d11_once = MP_STATIC_ONCE_INITIALIZER;
static PFN_D3D11_CREATE_DEVICE pD3D11CreateDevice = NULL;
static PFN_CREATE_DXGI_FACTORY pCreateDXGIFactory1 = NULL;
static void d3d11_load(void)
{
    HMODULE d3d11   = LoadLibraryW(L"d3d11.dll");
    HMODULE dxgilib = LoadLibraryW(L"dxgi.dll");
    if (!d3d11 || !dxgilib)
        return;

    pD3D11CreateDevice = (PFN_D3D11_CREATE_DEVICE)
        GetProcAddress(d3d11, "D3D11CreateDevice");
    pCreateDXGIFactory1 = (PFN_CREATE_DXGI_FACTORY)
        GetProcAddress(dxgilib, "CreateDXGIFactory1");
}

static bool load_d3d11_functions(struct mp_log *log)
{
    mp_exec_once(&d3d11_once, d3d11_load);
    if (!pD3D11CreateDevice || !pCreateDXGIFactory1) {
        mp_fatal(log, "Failed to load base d3d11 functionality: "
                      "CreateDevice: %s, CreateDXGIFactory1: %s\n",
                 pD3D11CreateDevice ? "success" : "failure",
                 pCreateDXGIFactory1 ? "success": "failure");
        return false;
    }

    return true;
}

#define D3D11_DXGI_ENUM(prefix, define) { case prefix ## define: return #define; }

static const char *d3d11_get_format_name(DXGI_FORMAT fmt)
{
    switch (fmt) {
    D3D11_DXGI_ENUM(DXGI_FORMAT_, UNKNOWN);
    D3D11_DXGI_ENUM(DXGI_FORMAT_, R32G32B32A32_TYPELESS);
    D3D11_DXGI_ENUM(DXGI_FORMAT_, R32G32B32A32_FLOAT);
    D3D11_DXGI_ENUM(DXGI_FORMAT_, R32G32B32A32_UINT);
    D3D11_DXGI_ENUM(DXGI_FORMAT_, R32G32B32A32_SINT);
    D3D11_DXGI_ENUM(DXGI_FORMAT_, R32G32B32_TYPELESS);
    D3D11_DXGI_ENUM(DXGI_FORMAT_, R32G32B32_FLOAT);
    D3D11_DXGI_ENUM(DXGI_FORMAT_, R32G32B32_UINT);
    D3D11_DXGI_ENUM(DXGI_FORMAT_, R32G32B32_SINT);
    D3D11_DXGI_ENUM(DXGI_FORMAT_, R16G16B16A16_TYPELESS);
    D3D11_DXGI_ENUM(DXGI_FORMAT_, R16G16B16A16_FLOAT);
    D3D11_DXGI_ENUM(DXGI_FORMAT_, R16G16B16A16_UNORM);
    D3D11_DXGI_ENUM(DXGI_FORMAT_, R16G16B16A16_UINT);
    D3D11_DXGI_ENUM(DXGI_FORMAT_, R16G16B16A16_SNORM);
    D3D11_DXGI_ENUM(DXGI_FORMAT_, R16G16B16A16_SINT);
    D3D11_DXGI_ENUM(DXGI_FORMAT_, R32G32_TYPELESS);
    D3D11_DXGI_ENUM(DXGI_FORMAT_, R32G32_FLOAT);
    D3D11_DXGI_ENUM(DXGI_FORMAT_, R32G32_UINT);
    D3D11_DXGI_ENUM(DXGI_FORMAT_, R32G32_SINT);
    D3D11_DXGI_ENUM(DXGI_FORMAT_, R32G8X24_TYPELESS);
    D3D11_DXGI_ENUM(DXGI_FORMAT_, D32_FLOAT_S8X24_UINT);
    D3D11_DXGI_ENUM(DXGI_FORMAT_, R32_FLOAT_X8X24_TYPELESS);
    D3D11_DXGI_ENUM(DXGI_FORMAT_, X32_TYPELESS_G8X24_UINT);
    D3D11_DXGI_ENUM(DXGI_FORMAT_, R10G10B10A2_TYPELESS);
    D3D11_DXGI_ENUM(DXGI_FORMAT_, R10G10B10A2_UNORM);
    D3D11_DXGI_ENUM(DXGI_FORMAT_, R10G10B10A2_UINT);
    D3D11_DXGI_ENUM(DXGI_FORMAT_, R11G11B10_FLOAT);
    D3D11_DXGI_ENUM(DXGI_FORMAT_, R8G8B8A8_TYPELESS);
    D3D11_DXGI_ENUM(DXGI_FORMAT_, R8G8B8A8_UNORM);
    D3D11_DXGI_ENUM(DXGI_FORMAT_, R8G8B8A8_UNORM_SRGB);
    D3D11_DXGI_ENUM(DXGI_FORMAT_, R8G8B8A8_UINT);
    D3D11_DXGI_ENUM(DXGI_FORMAT_, R8G8B8A8_SNORM);
    D3D11_DXGI_ENUM(DXGI_FORMAT_, R8G8B8A8_SINT);
    D3D11_DXGI_ENUM(DXGI_FORMAT_, R16G16_TYPELESS);
    D3D11_DXGI_ENUM(DXGI_FORMAT_, R16G16_FLOAT);
    D3D11_DXGI_ENUM(DXGI_FORMAT_, R16G16_UNORM);
    D3D11_DXGI_ENUM(DXGI_FORMAT_, R16G16_UINT);
    D3D11_DXGI_ENUM(DXGI_FORMAT_, R16G16_SNORM);
    D3D11_DXGI_ENUM(DXGI_FORMAT_, R16G16_SINT);
    D3D11_DXGI_ENUM(DXGI_FORMAT_, R32_TYPELESS);
    D3D11_DXGI_ENUM(DXGI_FORMAT_, D32_FLOAT);
    D3D11_DXGI_ENUM(DXGI_FORMAT_, R32_FLOAT);
    D3D11_DXGI_ENUM(DXGI_FORMAT_, R32_UINT);
    D3D11_DXGI_ENUM(DXGI_FORMAT_, R32_SINT);
    D3D11_DXGI_ENUM(DXGI_FORMAT_, R24G8_TYPELESS);
    D3D11_DXGI_ENUM(DXGI_FORMAT_, D24_UNORM_S8_UINT);
    D3D11_DXGI_ENUM(DXGI_FORMAT_, R24_UNORM_X8_TYPELESS);
    D3D11_DXGI_ENUM(DXGI_FORMAT_, X24_TYPELESS_G8_UINT);
    D3D11_DXGI_ENUM(DXGI_FORMAT_, R8G8_TYPELESS);
    D3D11_DXGI_ENUM(DXGI_FORMAT_, R8G8_UNORM);
    D3D11_DXGI_ENUM(DXGI_FORMAT_, R8G8_UINT);
    D3D11_DXGI_ENUM(DXGI_FORMAT_, R8G8_SNORM);
    D3D11_DXGI_ENUM(DXGI_FORMAT_, R8G8_SINT);
    D3D11_DXGI_ENUM(DXGI_FORMAT_, R16_TYPELESS);
    D3D11_DXGI_ENUM(DXGI_FORMAT_, R16_FLOAT);
    D3D11_DXGI_ENUM(DXGI_FORMAT_, D16_UNORM);
    D3D11_DXGI_ENUM(DXGI_FORMAT_, R16_UNORM);
    D3D11_DXGI_ENUM(DXGI_FORMAT_, R16_UINT);
    D3D11_DXGI_ENUM(DXGI_FORMAT_, R16_SNORM);
    D3D11_DXGI_ENUM(DXGI_FORMAT_, R16_SINT);
    D3D11_DXGI_ENUM(DXGI_FORMAT_, R8_TYPELESS);
    D3D11_DXGI_ENUM(DXGI_FORMAT_, R8_UNORM);
    D3D11_DXGI_ENUM(DXGI_FORMAT_, R8_UINT);
    D3D11_DXGI_ENUM(DXGI_FORMAT_, R8_SNORM);
    D3D11_DXGI_ENUM(DXGI_FORMAT_, R8_SINT);
    D3D11_DXGI_ENUM(DXGI_FORMAT_, A8_UNORM);
    D3D11_DXGI_ENUM(DXGI_FORMAT_, R1_UNORM);
    D3D11_DXGI_ENUM(DXGI_FORMAT_, R9G9B9E5_SHAREDEXP);
    D3D11_DXGI_ENUM(DXGI_FORMAT_, R8G8_B8G8_UNORM);
    D3D11_DXGI_ENUM(DXGI_FORMAT_, G8R8_G8B8_UNORM);
    D3D11_DXGI_ENUM(DXGI_FORMAT_, BC1_TYPELESS);
    D3D11_DXGI_ENUM(DXGI_FORMAT_, BC1_UNORM);
    D3D11_DXGI_ENUM(DXGI_FORMAT_, BC1_UNORM_SRGB);
    D3D11_DXGI_ENUM(DXGI_FORMAT_, BC2_TYPELESS);
    D3D11_DXGI_ENUM(DXGI_FORMAT_, BC2_UNORM);
    D3D11_DXGI_ENUM(DXGI_FORMAT_, BC2_UNORM_SRGB);
    D3D11_DXGI_ENUM(DXGI_FORMAT_, BC3_TYPELESS);
    D3D11_DXGI_ENUM(DXGI_FORMAT_, BC3_UNORM);
    D3D11_DXGI_ENUM(DXGI_FORMAT_, BC3_UNORM_SRGB);
    D3D11_DXGI_ENUM(DXGI_FORMAT_, BC4_TYPELESS);
    D3D11_DXGI_ENUM(DXGI_FORMAT_, BC4_UNORM);
    D3D11_DXGI_ENUM(DXGI_FORMAT_, BC4_SNORM);
    D3D11_DXGI_ENUM(DXGI_FORMAT_, BC5_TYPELESS);
    D3D11_DXGI_ENUM(DXGI_FORMAT_, BC5_UNORM);
    D3D11_DXGI_ENUM(DXGI_FORMAT_, BC5_SNORM);
    D3D11_DXGI_ENUM(DXGI_FORMAT_, B5G6R5_UNORM);
    D3D11_DXGI_ENUM(DXGI_FORMAT_, B5G5R5A1_UNORM);
    D3D11_DXGI_ENUM(DXGI_FORMAT_, B8G8R8A8_UNORM);
    D3D11_DXGI_ENUM(DXGI_FORMAT_, B8G8R8X8_UNORM);
    D3D11_DXGI_ENUM(DXGI_FORMAT_, R10G10B10_XR_BIAS_A2_UNORM);
    D3D11_DXGI_ENUM(DXGI_FORMAT_, B8G8R8A8_TYPELESS);
    D3D11_DXGI_ENUM(DXGI_FORMAT_, B8G8R8A8_UNORM_SRGB);
    D3D11_DXGI_ENUM(DXGI_FORMAT_, B8G8R8X8_TYPELESS);
    D3D11_DXGI_ENUM(DXGI_FORMAT_, B8G8R8X8_UNORM_SRGB);
    D3D11_DXGI_ENUM(DXGI_FORMAT_, BC6H_TYPELESS);
    D3D11_DXGI_ENUM(DXGI_FORMAT_, BC6H_UF16);
    D3D11_DXGI_ENUM(DXGI_FORMAT_, BC6H_SF16);
    D3D11_DXGI_ENUM(DXGI_FORMAT_, BC7_TYPELESS);
    D3D11_DXGI_ENUM(DXGI_FORMAT_, BC7_UNORM);
    D3D11_DXGI_ENUM(DXGI_FORMAT_, BC7_UNORM_SRGB);
    D3D11_DXGI_ENUM(DXGI_FORMAT_, AYUV);
    D3D11_DXGI_ENUM(DXGI_FORMAT_, Y410);
    D3D11_DXGI_ENUM(DXGI_FORMAT_, Y416);
    D3D11_DXGI_ENUM(DXGI_FORMAT_, NV12);
    D3D11_DXGI_ENUM(DXGI_FORMAT_, P010);
    D3D11_DXGI_ENUM(DXGI_FORMAT_, P016);
    D3D11_DXGI_ENUM(DXGI_FORMAT_, 420_OPAQUE);
    D3D11_DXGI_ENUM(DXGI_FORMAT_, YUY2);
    D3D11_DXGI_ENUM(DXGI_FORMAT_, Y210);
    D3D11_DXGI_ENUM(DXGI_FORMAT_, Y216);
    D3D11_DXGI_ENUM(DXGI_FORMAT_, NV11);
    D3D11_DXGI_ENUM(DXGI_FORMAT_, AI44);
    D3D11_DXGI_ENUM(DXGI_FORMAT_, IA44);
    D3D11_DXGI_ENUM(DXGI_FORMAT_, P8);
    D3D11_DXGI_ENUM(DXGI_FORMAT_, A8P8);
    D3D11_DXGI_ENUM(DXGI_FORMAT_, B4G4R4A4_UNORM);
    D3D11_DXGI_ENUM(DXGI_FORMAT_, P208);
    D3D11_DXGI_ENUM(DXGI_FORMAT_, V208);
    D3D11_DXGI_ENUM(DXGI_FORMAT_, V408);
    D3D11_DXGI_ENUM(DXGI_FORMAT_, FORCE_UINT);
    default:
        return "<Unknown>";
    }
}

static const char *d3d11_get_csp_name(DXGI_COLOR_SPACE_TYPE csp)
{
    switch (csp) {
    D3D11_DXGI_ENUM(DXGI_COLOR_SPACE_, RGB_FULL_G22_NONE_P709);
    D3D11_DXGI_ENUM(DXGI_COLOR_SPACE_, RGB_FULL_G10_NONE_P709);
    D3D11_DXGI_ENUM(DXGI_COLOR_SPACE_, RGB_STUDIO_G22_NONE_P709);
    D3D11_DXGI_ENUM(DXGI_COLOR_SPACE_, RGB_STUDIO_G22_NONE_P2020);
    D3D11_DXGI_ENUM(DXGI_COLOR_SPACE_, RESERVED);
    D3D11_DXGI_ENUM(DXGI_COLOR_SPACE_, YCBCR_FULL_G22_NONE_P709_X601);
    D3D11_DXGI_ENUM(DXGI_COLOR_SPACE_, YCBCR_STUDIO_G22_LEFT_P601);
    D3D11_DXGI_ENUM(DXGI_COLOR_SPACE_, YCBCR_FULL_G22_LEFT_P601);
    D3D11_DXGI_ENUM(DXGI_COLOR_SPACE_, YCBCR_STUDIO_G22_LEFT_P709);
    D3D11_DXGI_ENUM(DXGI_COLOR_SPACE_, YCBCR_FULL_G22_LEFT_P709);
    D3D11_DXGI_ENUM(DXGI_COLOR_SPACE_, YCBCR_STUDIO_G22_LEFT_P2020);
    D3D11_DXGI_ENUM(DXGI_COLOR_SPACE_, YCBCR_FULL_G22_LEFT_P2020);
    D3D11_DXGI_ENUM(DXGI_COLOR_SPACE_, RGB_FULL_G2084_NONE_P2020);
    D3D11_DXGI_ENUM(DXGI_COLOR_SPACE_, YCBCR_STUDIO_G2084_LEFT_P2020);
    D3D11_DXGI_ENUM(DXGI_COLOR_SPACE_, RGB_STUDIO_G2084_NONE_P2020);
    D3D11_DXGI_ENUM(DXGI_COLOR_SPACE_, YCBCR_STUDIO_G22_TOPLEFT_P2020);
    D3D11_DXGI_ENUM(DXGI_COLOR_SPACE_, YCBCR_STUDIO_G2084_TOPLEFT_P2020);
    D3D11_DXGI_ENUM(DXGI_COLOR_SPACE_, RGB_FULL_G22_NONE_P2020);
    D3D11_DXGI_ENUM(DXGI_COLOR_SPACE_, YCBCR_STUDIO_GHLG_TOPLEFT_P2020);
    D3D11_DXGI_ENUM(DXGI_COLOR_SPACE_, YCBCR_FULL_GHLG_TOPLEFT_P2020);
    D3D11_DXGI_ENUM(DXGI_COLOR_SPACE_, RGB_STUDIO_G24_NONE_P709);
    D3D11_DXGI_ENUM(DXGI_COLOR_SPACE_, RGB_STUDIO_G24_NONE_P2020);
    D3D11_DXGI_ENUM(DXGI_COLOR_SPACE_, YCBCR_STUDIO_G24_LEFT_P709);
    D3D11_DXGI_ENUM(DXGI_COLOR_SPACE_, YCBCR_STUDIO_G24_LEFT_P2020);
    D3D11_DXGI_ENUM(DXGI_COLOR_SPACE_, YCBCR_STUDIO_G24_TOPLEFT_P2020);
    D3D11_DXGI_ENUM(DXGI_COLOR_SPACE_, CUSTOM);
    default:
        return "<Unknown>";
    }
}

static bool d3d11_get_mp_csp(DXGI_COLOR_SPACE_TYPE csp,
                             struct pl_color_space *pl_color_system)
{
    if (!pl_color_system)
        return false;

    // Colorspaces utilizing gamma 2.2 (G22) are set to
    // AUTO as that keeps the current default flow regarding
    // SDR transfer function handling.
    // (no adjustment is done unless the user has a CMS LUT).
    //
    // Additionally, only set primary information with colorspaces
    // utilizing non-709 primaries to keep the current behavior
    // regarding not doing conversion from BT.601 to BT.709.
    switch (csp) {
    case DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709:
        *pl_color_system = (struct pl_color_space){
            .transfer  = PL_COLOR_TRC_UNKNOWN,
            .primaries = PL_COLOR_PRIM_UNKNOWN,
        };
        break;
    case DXGI_COLOR_SPACE_RGB_FULL_G10_NONE_P709:
        *pl_color_system = (struct pl_color_space) {
            .transfer  = PL_COLOR_TRC_LINEAR,
            .primaries = PL_COLOR_PRIM_UNKNOWN,
        };
        break;
    case DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020:
        *pl_color_system = (struct pl_color_space) {
            .transfer  = PL_COLOR_TRC_PQ,
            .primaries = PL_COLOR_PRIM_BT_2020,
        };
        break;
    case DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P2020:
        *pl_color_system = (struct pl_color_space) {
            .transfer  = PL_COLOR_TRC_UNKNOWN,
            .primaries = PL_COLOR_PRIM_BT_2020,
        };
        break;
    default:
        return false;
    }

    return true;
}

static bool query_output_format_and_colorspace(struct mp_log *log,
                                               IDXGISwapChain *swapchain,
                                               DXGI_FORMAT *out_fmt,
                                               DXGI_COLOR_SPACE_TYPE *out_cspace)
{
    IDXGIOutput *output = NULL;
    IDXGIOutput6 *output6 = NULL;
    DXGI_OUTPUT_DESC1 desc = { 0 };
    char *monitor_name = NULL;
    bool success = false;

    if (!out_fmt || !out_cspace)
        return false;

    HRESULT hr = IDXGISwapChain_GetContainingOutput(swapchain, &output);
    if (FAILED(hr)) {
        mp_err(log, "Failed to get swap chain's containing output: %s!\n",
               mp_HRESULT_to_str(hr));
        goto done;
    }

    hr = IDXGIOutput_QueryInterface(output, &IID_IDXGIOutput6,
                                    (void**)&output6);
    if (FAILED(hr)) {
        // point where systems older than Windows 10 would fail,
        // thus utilizing error log level only with windows 10+
        mp_msg(log, IsWindows10OrGreater() ? MSGL_ERR : MSGL_V,
               "Failed to create a DXGI 1.6 output interface: %s\n",
               mp_HRESULT_to_str(hr));
        goto done;
    }

    hr = IDXGIOutput6_GetDesc1(output6, &desc);
    if (FAILED(hr)) {
        mp_err(log, "Failed to query swap chain's output information: %s\n",
               mp_HRESULT_to_str(hr));
        goto done;
    }

    monitor_name = mp_to_utf8(NULL, desc.DeviceName);

    mp_verbose(log, "Queried output: %s, %ldx%ld @ %d bits, colorspace: %s (%d)\n",
               monitor_name,
               desc.DesktopCoordinates.right - desc.DesktopCoordinates.left,
               desc.DesktopCoordinates.bottom - desc.DesktopCoordinates.top,
               desc.BitsPerColor,
               d3d11_get_csp_name(desc.ColorSpace),
               desc.ColorSpace);

    *out_cspace = desc.ColorSpace;

    // limit ourselves to the 8bit and 10bit formats for now.
    // while the 16bit float format would be preferable as something
    // to default to, it seems to be hard-coded to linear transfer
    // in windowed mode, and follows configured colorspace in full screen.
    *out_fmt = desc.BitsPerColor > 8 ?
               DXGI_FORMAT_R10G10B10A2_UNORM : DXGI_FORMAT_R8G8B8A8_UNORM;

    success = true;

done:
    talloc_free(monitor_name);
    SAFE_RELEASE(output6);
    SAFE_RELEASE(output);
    return success;
}

// Get a const array of D3D_FEATURE_LEVELs from max_fl to min_fl (inclusive)
static int get_feature_levels(int max_fl, int min_fl,
                              const D3D_FEATURE_LEVEL **out)
{
    static const D3D_FEATURE_LEVEL levels[] = {
        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 int levels_len = MP_ARRAY_SIZE(levels);

    int start = 0;
    for (; start < levels_len; start++) {
        if (levels[start] <= max_fl)
            break;
    }
    int len = 0;
    for (; start + len < levels_len; len++) {
        if (levels[start + len] < min_fl)
            break;
    }
    *out = &levels[start];
    return len;
}

IDXGIAdapter1 *mp_get_dxgi_adapter(struct mp_log *log,
                                   bstr requested_adapter_name,
                                   bstr *listing)
{
    HRESULT hr = S_OK;
    IDXGIFactory1 *factory;
    IDXGIAdapter1 *picked_adapter = NULL;

    hr = pCreateDXGIFactory1(&IID_IDXGIFactory1, (void **)&factory);
    if (FAILED(hr)) {
        mp_fatal(log, "Failed to create a DXGI factory: %s\n",
                 mp_HRESULT_to_str(hr));
        return NULL;
    }

    for (unsigned int adapter_num = 0; hr != DXGI_ERROR_NOT_FOUND; adapter_num++)
    {
        IDXGIAdapter1 *adapter = NULL;
        DXGI_ADAPTER_DESC1 desc = { 0 };
        char *adapter_description = NULL;

        hr = IDXGIFactory1_EnumAdapters1(factory, adapter_num, &adapter);
        if (FAILED(hr)) {
            if (hr != DXGI_ERROR_NOT_FOUND) {
                mp_fatal(log, "Failed to enumerate at adapter %u\n",
                         adapter_num);
            }
            continue;
        }

        if (FAILED(IDXGIAdapter1_GetDesc1(adapter, &desc))) {
            mp_fatal(log, "Failed to get adapter description when listing at adapter %u\n",
                     adapter_num);
            continue;
        }

        adapter_description = mp_to_utf8(NULL, desc.Description);

        if (listing) {
            bstr_xappend_asprintf(NULL, listing,
                                  "Adapter %u: vendor: %u, description: %s\n",
                                  adapter_num, desc.VendorId,
                                  adapter_description);
        }

        if (requested_adapter_name.len &&
            bstr_case_startswith(bstr0(adapter_description),
                                 requested_adapter_name))
        {
            picked_adapter = adapter;
        }

        talloc_free(adapter_description);

        if (picked_adapter) {
            break;
        }

        SAFE_RELEASE(adapter);
    }

    SAFE_RELEASE(factory);

    return picked_adapter;
}

int mp_dxgi_validate_adapter(struct mp_log *log,
                             const struct m_option *opt,
                             struct bstr name, const char **value)
{
    struct bstr param = bstr0(*value);
    bool help = bstr_equals0(param, "help");
    bool adapter_matched = false;
    struct bstr listing = { 0 };

    if (bstr_equals0(param, "")) {
        return 0;
    }

    adapter_matched = mp_dxgi_list_or_verify_adapters(log,
                                                      help ? bstr0(NULL) : param,
                                                      help ? &listing : NULL);

    if (help) {
        mp_info(log, "Available DXGI adapters:\n%.*s",
                BSTR_P(listing));
        talloc_free(listing.start);
        return M_OPT_EXIT;
    }

    if (!adapter_matched) {
        mp_err(log, "No adapter matching '%.*s'!\n", BSTR_P(param));
    }

    return adapter_matched ? 0 : M_OPT_INVALID;
}

static HRESULT create_device(struct mp_log *log, IDXGIAdapter1 *adapter,
                             bool warp, bool debug, int max_fl, int min_fl,
                             ID3D11Device **dev)
{
    const D3D_FEATURE_LEVEL *levels;
    int levels_len = get_feature_levels(max_fl, min_fl, &levels);
    if (!levels_len) {
        mp_fatal(log, "No suitable Direct3D feature level found\n");
        return E_FAIL;
    }

    D3D_DRIVER_TYPE type = warp ? D3D_DRIVER_TYPE_WARP
                                : D3D_DRIVER_TYPE_HARDWARE;
    UINT flags = debug ? D3D11_CREATE_DEVICE_DEBUG : 0;
    return pD3D11CreateDevice((IDXGIAdapter *)adapter, adapter ? D3D_DRIVER_TYPE_UNKNOWN : type,
                              NULL, flags, levels, levels_len, D3D11_SDK_VERSION, dev, NULL, NULL);
}

bool mp_dxgi_list_or_verify_adapters(struct mp_log *log,
                                     bstr adapter_name,
                                     bstr *listing)
{
    IDXGIAdapter1 *picked_adapter = NULL;

    if (!load_d3d11_functions(log)) {
        return false;
    }

    if ((picked_adapter = mp_get_dxgi_adapter(log, adapter_name, listing))) {
        SAFE_RELEASE(picked_adapter);
        return true;
    }

    return false;
}

// Create a Direct3D 11 device for rendering and presentation. This is meant to
// reduce boilerplate in backends that D3D11, while also making sure they share
// the same device creation logic and log the same information.
bool mp_d3d11_create_present_device(struct mp_log *log,
                                    struct d3d11_device_opts *opts,
                                    ID3D11Device **dev_out)
{
    bool debug = opts->debug;
    bool warp = opts->force_warp;
    int max_fl = opts->max_feature_level;
    int min_fl = opts->min_feature_level;
    // Normalize nullptr and an empty string to nullptr to simplify handling.
    char *adapter_name = (opts->adapter_name && *(opts->adapter_name)) ?
                         opts->adapter_name : NULL;
    ID3D11Device *dev = NULL;
    IDXGIDevice1 *dxgi_dev = NULL;
    IDXGIAdapter1 *adapter = NULL;
    bool success = false;
    HRESULT hr;

    if (!load_d3d11_functions(log)) {
        goto done;
    }

    adapter = mp_get_dxgi_adapter(log, bstr0(adapter_name), NULL);

    if (adapter_name && !adapter) {
        mp_warn(log, "Adapter matching '%s' was not found in the system! "
                     "Will fall back to the default adapter.\n",
                 adapter_name);
    }

    // Return here to retry creating the device
    do {
        // Use these default feature levels if they are not set
        max_fl = max_fl ? max_fl : D3D_FEATURE_LEVEL_11_0;
        min_fl = min_fl ? min_fl : D3D_FEATURE_LEVEL_9_1;

        hr = create_device(log, adapter, warp, debug, max_fl, min_fl, &dev);

        // Retry without debug, if SDK is not available
        if (debug && hr == DXGI_ERROR_SDK_COMPONENT_MISSING) {
            mp_warn(log, "gpu-debug disabled due to error: %s\n", mp_HRESULT_to_str(hr));
            debug = false;
            continue;
        }

        if (SUCCEEDED(hr))
            break;

        // Trying to create a D3D_FEATURE_LEVEL_12_0 device on Windows 8.1 or
        // below will not succeed. Try an 11_1 device.
        if (max_fl >= D3D_FEATURE_LEVEL_12_0 &&
            min_fl <= D3D_FEATURE_LEVEL_11_1)
        {
            mp_dbg(log, "Failed to create 12_0+ device, trying 11_1\n");
            max_fl = D3D_FEATURE_LEVEL_11_1;
            continue;
        }

        // Trying to create a D3D_FEATURE_LEVEL_11_1 device on Windows 7
        // without the platform update will not succeed. Try an 11_0 device.
        if (max_fl >= D3D_FEATURE_LEVEL_11_1 &&
            min_fl <= D3D_FEATURE_LEVEL_11_0)
        {
            mp_dbg(log, "Failed to create 11_1+ device, trying 11_0\n");
            max_fl = D3D_FEATURE_LEVEL_11_0;
            continue;
        }

        // Retry with WARP if allowed
        if (!warp && opts->allow_warp) {
            mp_dbg(log, "Failed to create hardware device, trying WARP\n");
            warp = true;
            max_fl = opts->max_feature_level;
            min_fl = opts->min_feature_level;
            continue;
        }

        mp_fatal(log, "Failed to create Direct3D 11 device: %s\n",
                 mp_HRESULT_to_str(hr));
        goto done;
    } while (true);

    // if we picked an adapter, release it here - we're taking another
    // from the device.
    SAFE_RELEASE(adapter);

    hr = ID3D11Device_QueryInterface(dev, &IID_IDXGIDevice1, (void**)&dxgi_dev);
    if (FAILED(hr)) {
        mp_fatal(log, "Failed to get DXGI device\n");
        goto done;
    }
    hr = IDXGIDevice1_GetParent(dxgi_dev, &IID_IDXGIAdapter1, (void**)&adapter);
    if (FAILED(hr)) {
        mp_fatal(log, "Failed to get DXGI adapter\n");
        goto done;
    }

    IDXGIDevice1_SetMaximumFrameLatency(dxgi_dev, opts->max_frame_latency);

    DXGI_ADAPTER_DESC1 desc;
    hr = IDXGIAdapter1_GetDesc1(adapter, &desc);
    if (FAILED(hr)) {
        mp_fatal(log, "Failed to get adapter description\n");
        goto done;
    }

    D3D_FEATURE_LEVEL selected_level = ID3D11Device_GetFeatureLevel(dev);
    mp_verbose(log, "Using Direct3D 11 feature level %u_%u\n",
               ((unsigned)selected_level) >> 12,
               (((unsigned)selected_level) >> 8) & 0xf);

    char *dev_name = mp_to_utf8(NULL, desc.Description);
    mp_verbose(log, "Device Name: %s\n"
                    "Device ID: %04x:%04x (rev %02x)\n"
                    "Subsystem ID: %04x:%04x\n"
                    "LUID: %08lx%08lx\n",
               dev_name,
               desc.VendorId, desc.DeviceId, desc.Revision,
               LOWORD(desc.SubSysId), HIWORD(desc.SubSysId),
               desc.AdapterLuid.HighPart, desc.AdapterLuid.LowPart);
    talloc_free(dev_name);

    if (desc.Flags & DXGI_ADAPTER_FLAG_SOFTWARE)
        warp = true;
    // If the primary display adapter is a software adapter, the
    // DXGI_ADAPTER_FLAG_SOFTWARE flag won't be set, but the device IDs should
    // still match the Microsoft Basic Render Driver
    if (desc.VendorId == 0x1414 && desc.DeviceId == 0x8c)
        warp = true;
    if (warp) {
        mp_msg(log, opts->force_warp ? MSGL_V : MSGL_WARN,
               "Using a software adapter\n");
    }

    *dev_out = dev;
    dev = NULL;
    success = true;

done:
    SAFE_RELEASE(adapter);
    SAFE_RELEASE(dxgi_dev);
    SAFE_RELEASE(dev);
    return success;
}

static HRESULT create_swapchain_1_2(ID3D11Device *dev, IDXGIFactory2 *factory,
                                    struct mp_log *log,
                                    struct d3d11_swapchain_opts *opts,
                                    bool flip, DXGI_FORMAT format,
                                    IDXGISwapChain **swapchain_out)
{
    IDXGISwapChain *swapchain = NULL;
    IDXGISwapChain1 *swapchain1 = NULL;
    HRESULT hr;

    DXGI_SWAP_CHAIN_DESC1 desc = {
        .Width = opts->width ? opts->width : 1,
        .Height = opts->height ? opts->height : 1,
        .Format = format,
        .SampleDesc = { .Count = 1 },
        .BufferUsage = opts->usage,
    };

    if (flip) {
        // UNORDERED_ACCESS with FLIP_SEQUENTIAL seems to be buggy with
        // Windows 7 drivers
        if ((desc.BufferUsage & DXGI_USAGE_UNORDERED_ACCESS) &&
            !IsWindows8OrGreater())
        {
            mp_verbose(log, "Disabling UNORDERED_ACCESS for flip-model "
                            "swapchain backbuffers in Windows 7\n");
            desc.BufferUsage &= ~DXGI_USAGE_UNORDERED_ACCESS;
        }

        if (IsWindows10OrGreater()) {
            desc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
        } else {
            desc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL;
        }
        desc.BufferCount = opts->length;
    } else {
        desc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;
        desc.BufferCount = 1;
    }

    hr = IDXGIFactory2_CreateSwapChainForHwnd(factory, (IUnknown*)dev,
        opts->window, &desc, NULL, NULL, &swapchain1);
    if (FAILED(hr))
        goto done;
    hr = IDXGISwapChain1_QueryInterface(swapchain1, &IID_IDXGISwapChain,
                                        (void**)&swapchain);
    if (FAILED(hr))
        goto done;

    *swapchain_out = swapchain;
    swapchain = NULL;

done:
    SAFE_RELEASE(swapchain1);
    SAFE_RELEASE(swapchain);
    return hr;
}

static HRESULT create_swapchain_1_1(ID3D11Device *dev, IDXGIFactory1 *factory,
                                    struct mp_log *log,
                                    struct d3d11_swapchain_opts *opts,
                                    DXGI_FORMAT format,
                                    IDXGISwapChain **swapchain_out)
{
    DXGI_SWAP_CHAIN_DESC desc = {
        .BufferDesc = {
            .Width = opts->width ? opts->width : 1,
            .Height = opts->height ? opts->height : 1,
            .Format = format,
        },
        .SampleDesc = { .Count = 1 },
        .BufferUsage = opts->usage,
        .BufferCount = 1,
        .OutputWindow = opts->window,
        .Windowed = TRUE,
        .SwapEffect = DXGI_SWAP_EFFECT_DISCARD,
    };

    return IDXGIFactory1_CreateSwapChain(factory, (IUnknown*)dev, &desc,
                                         swapchain_out);
}

static bool update_swapchain_format(struct mp_log *log,
                                    IDXGISwapChain *swapchain,
                                    DXGI_FORMAT format)
{
    DXGI_SWAP_CHAIN_DESC desc;

    HRESULT hr = IDXGISwapChain_GetDesc(swapchain, &desc);
    if (FAILED(hr)) {
        mp_fatal(log, "Failed to query swap chain's current state: %s\n",
                 mp_HRESULT_to_str(hr));
        return false;
    }

    hr = IDXGISwapChain_ResizeBuffers(swapchain, 0, desc.BufferDesc.Width,
                                      desc.BufferDesc.Height,
                                      format, 0);
    if (FAILED(hr)) {
        mp_fatal(log, "Couldn't update swapchain format: %s\n",
                 mp_HRESULT_to_str(hr));
        return false;
    }

    return true;
}

static bool update_swapchain_color_space(struct mp_log *log,
                                         IDXGISwapChain *swapchain,
                                         DXGI_COLOR_SPACE_TYPE color_space)
{
    IDXGISwapChain4 *swapchain4 = NULL;
    const char *csp_name = d3d11_get_csp_name(color_space);
    bool success = false;
    HRESULT hr = E_FAIL;
    unsigned int csp_support_flags;

    hr = IDXGISwapChain_QueryInterface(swapchain, &IID_IDXGISwapChain4,
                                       (void *)&(swapchain4));
    if (FAILED(hr)) {
        mp_err(log, "Failed to create v4 swapchain for color space "
                    "configuration (%s)!\n",
               mp_HRESULT_to_str(hr));
        goto done;
    }

    hr = IDXGISwapChain4_CheckColorSpaceSupport(swapchain4,
                                                color_space,
                                                &csp_support_flags);
    if (FAILED(hr)) {
        mp_err(log, "Failed to check color space support for color space "
                    "%s (%d): %s!\n",
               csp_name, color_space, mp_HRESULT_to_str(hr));
        goto done;
    }

    mp_verbose(log,
               "Swapchain capabilities for color space %s (%d): "
               "normal: %s, overlay: %s\n",
               csp_name, color_space,
               (csp_support_flags & DXGI_SWAP_CHAIN_COLOR_SPACE_SUPPORT_FLAG_PRESENT) ?
               "yes" : "no",
               (csp_support_flags & DXGI_SWAP_CHAIN_COLOR_SPACE_SUPPORT_FLAG_OVERLAY_PRESENT) ?
               "yes" : "no");

    if (!(csp_support_flags & DXGI_SWAP_CHAIN_COLOR_SPACE_SUPPORT_FLAG_PRESENT)) {
        mp_err(log, "Color space %s (%d) is not supported by this swapchain!\n",
               csp_name, color_space);
        goto done;
    }

    hr = IDXGISwapChain4_SetColorSpace1(swapchain4, color_space);
    if (FAILED(hr)) {
        mp_err(log, "Failed to set color space %s (%d) for this swapchain "
                    "(%s)!\n",
               csp_name, color_space, mp_HRESULT_to_str(hr));
        goto done;
    }

    mp_verbose(log, "Swapchain successfully configured to color space %s (%d)!\n",
               csp_name, color_space);

    success = true;

done:
    SAFE_RELEASE(swapchain4);
    return success;
}

static bool configure_created_swapchain(struct mp_log *log,
                                        IDXGISwapChain *swapchain,
                                        DXGI_FORMAT requested_format,
                                        DXGI_COLOR_SPACE_TYPE requested_csp,
                                        struct pl_color_space *configured_csp)
{
    DXGI_FORMAT probed_format = DXGI_FORMAT_UNKNOWN;
    DXGI_FORMAT selected_format = DXGI_FORMAT_UNKNOWN;
    DXGI_COLOR_SPACE_TYPE probed_colorspace = DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709;
    DXGI_COLOR_SPACE_TYPE selected_colorspace;
    const char *format_name = NULL;
    const char *csp_name = NULL;
    struct pl_color_space pl_color_system = { 0 };
    bool mp_csp_mapped = false;

    query_output_format_and_colorspace(log, swapchain,
                                       &probed_format,
                                       &probed_colorspace);


    selected_format = requested_format != DXGI_FORMAT_UNKNOWN ?
                      requested_format :
                      (probed_format != DXGI_FORMAT_UNKNOWN ?
                       probed_format : DXGI_FORMAT_R8G8B8A8_UNORM);
    selected_colorspace = requested_csp != -1 ?
                          requested_csp : probed_colorspace;
    format_name   = d3d11_get_format_name(selected_format);
    csp_name      = d3d11_get_csp_name(selected_colorspace);
    mp_csp_mapped = d3d11_get_mp_csp(selected_colorspace, &pl_color_system);

    mp_verbose(log, "Selected swapchain format %s (%d), attempting "
                    "to utilize it.\n",
               format_name, selected_format);

    if (!update_swapchain_format(log, swapchain, selected_format)) {
        return false;
    }

    if (!IsWindows10OrGreater()) {
        // On older than Windows 10, query_output_format_and_colorspace
        // will not change probed_colorspace, and even if a user sets
        // a colorspace it will not get applied. Thus warn user in case a
        // value was specifically set and finish.
        if (requested_csp != -1) {
            mp_warn(log, "User selected a D3D11 color space %s (%d), "
                         "but configuration of color spaces is only supported"
                         "from Windows 10! The default configuration has been "
                         "left as-is.\n",
                    csp_name, selected_colorspace);
        }

        return true;
    }

    if (!mp_csp_mapped) {
        mp_warn(log, "Color space %s (%d) does not have an mpv color space "
                     "mapping! Overriding to standard sRGB!\n",
                csp_name, selected_colorspace);
        selected_colorspace = DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709;
        d3d11_get_mp_csp(selected_colorspace, &pl_color_system);
    }

    mp_verbose(log, "Selected swapchain color space %s (%d), attempting to "
                    "utilize it.\n",
               csp_name, selected_colorspace);

    if (!update_swapchain_color_space(log, swapchain, selected_colorspace)) {
        return false;
    }

    if (configured_csp) {
        *configured_csp = pl_color_system;
    }

    return true;
}

// Create a Direct3D 11 swapchain
bool mp_d3d11_create_swapchain(ID3D11Device *dev, struct mp_log *log,
                               struct d3d11_swapchain_opts *opts,
                               IDXGISwapChain **swapchain_out)
{
    IDXGIDevice1 *dxgi_dev = NULL;
    IDXGIAdapter1 *adapter = NULL;
    IDXGIFactory1 *factory = NULL;
    IDXGIFactory2 *factory2 = NULL;
    IDXGISwapChain *swapchain = NULL;
    bool success = false;
    HRESULT hr;

    hr = ID3D11Device_QueryInterface(dev, &IID_IDXGIDevice1, (void**)&dxgi_dev);
    if (FAILED(hr)) {
        mp_fatal(log, "Failed to get DXGI device\n");
        goto done;
    }
    hr = IDXGIDevice1_GetParent(dxgi_dev, &IID_IDXGIAdapter1, (void**)&adapter);
    if (FAILED(hr)) {
        mp_fatal(log, "Failed to get DXGI adapter\n");
        goto done;
    }
    hr = IDXGIAdapter1_GetParent(adapter, &IID_IDXGIFactory1, (void**)&factory);
    if (FAILED(hr)) {
        mp_fatal(log, "Failed to get DXGI factory\n");
        goto done;
    }
    hr = IDXGIFactory1_QueryInterface(factory, &IID_IDXGIFactory2,
                                      (void**)&factory2);
    if (FAILED(hr))
        factory2 = NULL;

    bool flip = factory2 && opts->flip;

    // Return here to retry creating the swapchain
    do {
        if (factory2) {
            // Create a DXGI 1.2+ (Windows 8+) swap chain if possible
            hr = create_swapchain_1_2(dev, factory2, log, opts, flip,
                                      DXGI_FORMAT_R8G8B8A8_UNORM, &swapchain);
        } else {
            // Fall back to DXGI 1.1 (Windows 7)
            hr = create_swapchain_1_1(dev, factory, log, opts,
                                      DXGI_FORMAT_R8G8B8A8_UNORM, &swapchain);
        }
        if (SUCCEEDED(hr))
            break;

        if (flip) {
            mp_dbg(log, "Failed to create flip-model swapchain, trying bitblt\n");
            flip = false;
            continue;
        }

        mp_fatal(log, "Failed to create swapchain: %s\n", mp_HRESULT_to_str(hr));
        goto done;
    } while (true);

    // Prevent DXGI from making changes to the VO window, otherwise it will
    // hook the Alt+Enter keystroke and make it trigger an ugly transition to
    // exclusive fullscreen mode instead of running the user-set command.
    IDXGIFactory_MakeWindowAssociation(factory, opts->window,
        DXGI_MWA_NO_WINDOW_CHANGES | DXGI_MWA_NO_ALT_ENTER |
        DXGI_MWA_NO_PRINT_SCREEN);

    if (factory2) {
        mp_verbose(log, "Using DXGI 1.2+\n");
    } else {
        mp_verbose(log, "Using DXGI 1.1\n");
    }

    configure_created_swapchain(log, swapchain, opts->format,
                                opts->color_space,
                                opts->configured_csp);

    DXGI_SWAP_CHAIN_DESC scd = {0};
    IDXGISwapChain_GetDesc(swapchain, &scd);
    if (scd.SwapEffect == DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL ||
        scd.SwapEffect == DXGI_SWAP_EFFECT_FLIP_DISCARD)
    {
        mp_verbose(log, "Using flip-model presentation\n");
    } else {
        mp_verbose(log, "Using bitblt-model presentation\n");
    }

    *swapchain_out = swapchain;
    swapchain = NULL;
    success = true;

done:
    SAFE_RELEASE(swapchain);
    SAFE_RELEASE(factory2);
    SAFE_RELEASE(factory);
    SAFE_RELEASE(adapter);
    SAFE_RELEASE(dxgi_dev);
    return success;
}
