/*****************************************************************************
 * d3d11_fmt.cpp : D3D11 helper calls
 *****************************************************************************
 * Copyright © 2017 VLC authors, VideoLAN and VideoLabs
 *
 * Authors: Steve Lhomme <robux4@gmail.com>
 *
 * This program 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 program 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 program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
 *****************************************************************************/

#include <process.h>
#include <winapifamily.h>
#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)
#define BUILD_FOR_UAP 0
#else
#define BUILD_FOR_UAP 1
#endif
#if defined(WINAPI_FAMILY)
# undef WINAPI_FAMILY
#endif
#define WINAPI_FAMILY WINAPI_FAMILY_DESKTOP_APP

#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

#if !BUILD_FOR_UAP
#if !defined(NDEBUG) && defined(HAVE_DXGIDEBUG_H)
#define HAVE_DXGI_DEBUG 1
#endif
#endif

#include <vlc_common.h>
#include <vlc_picture.h>
#include <vlc_charset.h>

#include <vlc/libvlc.h>
#include <vlc/libvlc_picture.h>
#include <vlc/libvlc_media.h>
#include <vlc/libvlc_renderer_discoverer.h>
#include <vlc/libvlc_media_player.h>

#include <initguid.h>
#include <d3d11.h>
#include <dxgi1_2.h>
#ifdef HAVE_DXGI_DEBUG
# include <dxgidebug.h>
#endif
#include <assert.h>

#include "d3d11_fmt.h"

#include <wbemidl.h>

#define D3D11_PICCONTEXT_FROM_PICCTX(pic_ctx)  \
    container_of((pic_ctx), d3d11_pic_context, s)

picture_sys_d3d11_t *ActiveD3D11PictureSys(picture_t *pic)
{
    assert(pic->context != NULL);
    assert(pic->p_sys == NULL);
    d3d11_pic_context *pic_ctx = D3D11_PICCONTEXT_FROM_PICCTX(pic->context);
    return &pic_ctx->picsys;
}

void AcquireD3D11PictureSys(picture_sys_d3d11_t *p_sys)
{
    for (int i=0; i<DXGI_MAX_SHADER_VIEW; i++) {
        if (p_sys->renderSrc[i])
            p_sys->renderSrc[i]->AddRef();
        if (p_sys->texture[i])
            p_sys->texture[i]->AddRef();
    }
    if (p_sys->processorInput)
        p_sys->processorInput->AddRef();
    if (p_sys->processorOutput)
        p_sys->processorOutput->AddRef();
}

void ReleaseD3D11PictureSys(picture_sys_d3d11_t *p_sys)
{
    for (int i=0; i<DXGI_MAX_SHADER_VIEW; i++) {
        if (p_sys->renderSrc[i])
            p_sys->renderSrc[i]->Release();
        if (p_sys->texture[i])
            p_sys->texture[i]->Release();
    }
    if (p_sys->processorInput)
        p_sys->processorInput->Release();
    if (p_sys->processorOutput)
        p_sys->processorOutput->Release();
}

/* map texture planes to resource views */
int D3D11_AllocateResourceView(struct vlc_logger *obj, ID3D11Device *d3ddevice,
                              const d3d_format_t *format,
                              ID3D11Texture2D *p_texture[DXGI_MAX_SHADER_VIEW], UINT slice_index,
                              ID3D11ShaderResourceView *renderSrc[DXGI_MAX_SHADER_VIEW])
{
    HRESULT hr;
    int i;
    D3D11_SHADER_RESOURCE_VIEW_DESC resviewDesc{};
    D3D11_TEXTURE2D_DESC texDesc;
    p_texture[0]->GetDesc(&texDesc);
    assert(texDesc.BindFlags & D3D11_BIND_SHADER_RESOURCE);

    if (texDesc.ArraySize == 1)
    {
        resviewDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D;
        resviewDesc.Texture2D.MipLevels = 1;
    }
    else
    {
        resviewDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2DARRAY;
        resviewDesc.Texture2DArray.MipLevels = -1;
        resviewDesc.Texture2DArray.ArraySize = 1;
        resviewDesc.Texture2DArray.FirstArraySlice = slice_index;
        assert(slice_index < texDesc.ArraySize);
    }
    for (i=0; i<DXGI_MAX_SHADER_VIEW; i++)
    {
        resviewDesc.Format = format->resourceFormat[i];
        if (resviewDesc.Format == DXGI_FORMAT_UNKNOWN)
            renderSrc[i] = NULL;
        else
        {
            hr = d3ddevice->CreateShaderResourceView(p_texture[i], &resviewDesc, &renderSrc[i]);
            if (FAILED(hr)) {
                vlc_error(obj, "Could not Create the Texture ResourceView %d slice %d. (hr=0x%lX)", i, slice_index, hr);
                break;
            }
        }
    }

    if (i != DXGI_MAX_SHADER_VIEW)
    {
        while (--i >= 0)
        {
            renderSrc[i]->Release();
            renderSrc[i] = NULL;
        }
        return VLC_EGENERIC;
    }

    return VLC_SUCCESS;
}

static LARGE_INTEGER D3D11_GetSystemDriver(vlc_object_t *obj, d3d11_device_t *d3d_dev)
{
    HRESULT hr;
    LARGE_INTEGER result = {};
    IWbemLocator *pLoc = NULL;
    IWbemServices *pSvc = NULL;
    IEnumWbemClassObject* pEnumerator = NULL;

    BSTR bRootNamespace = SysAllocString(L"ROOT\\CIMV2");
    BSTR bWQL = SysAllocString(L"WQL");

    WCHAR lookup[256];
    _snwprintf(lookup, ARRAY_SIZE(lookup),
               L"SELECT * FROM Win32_VideoController WHERE PNPDeviceID LIKE 'PCI\\\\VEN_%04X&DEV_%04X&SUBSYS_%08X&REV_%02X%%'",
               d3d_dev->adapterDesc.VendorId, d3d_dev->adapterDesc.DeviceId,
               d3d_dev->adapterDesc.SubSysId, d3d_dev->adapterDesc.Revision);
    BSTR bVideoController = SysAllocString(lookup);

    hr = CoInitializeEx(NULL, COINIT_MULTITHREADED | COINIT_DISABLE_OLE1DDE);
    if (FAILED(hr))
    {
        msg_Dbg(obj, "Unable to initialize COM library");
        SysFreeString(bRootNamespace);
        SysFreeString(bWQL);
        SysFreeString(bVideoController);
        return {};
    }

    IWbemClassObject *pclsObj = NULL;
    ULONG uReturn = 0;
    MULTI_QI res{};
    res.pIID = &IID_IWbemLocator;
#if !BUILD_FOR_UAP
    hr = CoCreateInstanceEx(CLSID_WbemLocator, NULL, CLSCTX_INPROC_SERVER, 0,
                                1, &res);
#else // BUILD_FOR_UAP
    hr = CoCreateInstanceFromApp(CLSID_WbemLocator, NULL, CLSCTX_INPROC_SERVER, 0,
                                1, &res);
#endif // BUILD_FOR_UAP
    if (FAILED(hr) || FAILED(res.hr))
    {
        msg_Dbg(obj, "Failed to create IWbemLocator object");
        goto done;
    }
    pLoc = static_cast<IWbemLocator *>(res.pItf);

    hr = pLoc->ConnectServer(bRootNamespace,
                                    NULL, NULL, NULL, 0, NULL, NULL, &pSvc);
    if (FAILED(hr))
    {
        msg_Dbg(obj, "Could not connect to namespace");
        goto done;
    }

#if !BUILD_FOR_UAP
    hr = CoSetProxyBlanket(
        pSvc,
        RPC_C_AUTHN_WINNT,
        RPC_C_AUTHZ_NONE,
        NULL,
        RPC_C_AUTHN_LEVEL_CALL,
        RPC_C_IMP_LEVEL_IMPERSONATE,
        NULL,
        EOAC_NONE
    );
    if (FAILED(hr))
    {
        msg_Dbg(obj, "Could not set proxy blanket");
        goto done;
    }
#endif // !BUILD_FOR_UAP

    hr =  pSvc->ExecQuery(bWQL, bVideoController,
        WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY, NULL, &pEnumerator);
    if (FAILED(hr) || !pEnumerator)
    {
        msg_Dbg(obj, "Query for Win32_VideoController failed");
        goto done;
    }

    hr = pEnumerator->Next(WBEM_INFINITE, 1, &pclsObj, &uReturn);
    if (!uReturn)
    {
        msg_Warn(obj, "failed to find the device driver");
        goto done;
    }

    VARIANT vtProp;
    VariantInit(&vtProp);

    hr = pclsObj->Get(L"DriverVersion", 0, &vtProp, 0, 0);
    if ( FAILED( hr ) )
    {
        msg_Warn(obj, "failed to read the driver version");
        pclsObj->Release();
        goto done;
    }

    int wddm, d3d_features, revision, build;
    /* see https://docs.microsoft.com/en-us/windows-hardware/drivers/display/wddm-2-1-features#driver-versioning */
    if (swscanf(vtProp.bstrVal, TEXT("%d.%d.%d.%d"), &wddm, &d3d_features, &revision, &build) != 4)
    {
        msg_Warn(obj, "the adapter DriverVersion '%ls' doesn't match the expected format", vtProp.bstrVal);
    }
    else
    {
        result.HighPart = (wddm << 16) + d3d_features;
        result.LowPart  = (revision << 16) + build;
    }

    VariantClear(&vtProp);
    pclsObj->Release();

done:
    SysFreeString(bRootNamespace);
    SysFreeString(bWQL);
    SysFreeString(bVideoController);
    if (pEnumerator)
        pEnumerator->Release();
    if (pSvc)
        pSvc->Release();
    if (pLoc)
        pLoc->Release();
    CoUninitialize();

    return result;
}

static void D3D11_GetDriverVersion(vlc_object_t *obj, d3d11_device_t *d3d_dev, IDXGIAdapter *pAdapter)
{
    int wddm, d3d_features, revision, build;
    HRESULT hr;
    LARGE_INTEGER driver = {};

    hr = pAdapter->CheckInterfaceSupport(__uuidof(IDXGIDevice), &driver);
    if (FAILED(hr))
    {
        msg_Dbg(obj, "failed to get interface version. (hr=0x%lX)", hr);
        driver = D3D11_GetSystemDriver(obj, d3d_dev);
    }
    else if (HIWORD(driver.HighPart) < 23)
    // starting with WDDM 2.3 driver versions must be coherent
    // https://learn.microsoft.com/en-us/windows/win32/api/dxgi/nf-dxgi-idxgiadapter-checkinterfacesupport#parameters
    {
        msg_Dbg(obj, "unsupported interface version %" PRIx64, driver.QuadPart);
        driver = D3D11_GetSystemDriver(obj, d3d_dev);
    }
    else
    {
#ifndef NDEBUG
        const auto check_old_way = D3D11_GetSystemDriver(obj, d3d_dev);
        assert(driver.QuadPart == check_old_way.QuadPart || check_old_way.QuadPart == 0);
#endif
    }

    wddm         = HIWORD(driver.HighPart);
    d3d_features = LOWORD(driver.HighPart);
    revision     = HIWORD(driver.LowPart);
    build        = LOWORD(driver.LowPart);

    msg_Dbg(obj, "%s WDDM driver %d.%d.%d.%d", DxgiVendorStr(d3d_dev->adapterDesc.VendorId), wddm, d3d_features, revision, build);
    if (d3d_dev->adapterDesc.VendorId == GPU_MANUFACTURER_INTEL && revision >= 100)
    {
        /* new Intel driver format */
        build += (revision - 100) * 1000;
    }
    d3d_dev->WDDM.wddm         = wddm;
    d3d_dev->WDDM.d3d_features = d3d_features;
    d3d_dev->WDDM.revision     = revision;
    d3d_dev->WDDM.build        = build;
}

#ifdef HAVE_DXGI_DEBUG
struct dxgi_debug_handle_t
{
    HINSTANCE                 dxgidebug_dll;
    HRESULT (WINAPI * pf_DXGIGetDebugInterface)(REFIID, void **ppDebug);

    void Init()
    {
        dxgidebug_dll = nullptr;
        pf_DXGIGetDebugInterface = nullptr;
        if (IsDebuggerPresent())
        {
            dxgidebug_dll = LoadLibraryExA("DXGIDEBUG.DLL", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32);
            if (dxgidebug_dll)
            {
                pf_DXGIGetDebugInterface =
                        reinterpret_cast<decltype(pf_DXGIGetDebugInterface)>(GetProcAddress(dxgidebug_dll, "DXGIGetDebugInterface"));
                if (unlikely(!pf_DXGIGetDebugInterface))
                {
                    FreeLibrary(dxgidebug_dll);
                    dxgidebug_dll = nullptr;
                }
            }
        }
    }

    void Release()
    {
        if (dxgidebug_dll)
            FreeLibrary(dxgidebug_dll);
    }

    void LogResource()
    {
        if (pf_DXGIGetDebugInterface)
        {
            IDXGIDebug *pDXGIDebug;
            if (SUCCEEDED(pf_DXGIGetDebugInterface(IID_GRAPHICS_PPV_ARGS(&pDXGIDebug))))
            {
                pDXGIDebug->ReportLiveObjects(DXGI_DEBUG_ALL, DXGI_DEBUG_RLO_ALL);
                pDXGIDebug->Release();
            }
        }
    }
};
#endif

typedef struct {
    struct {
        void                            *opaque;
        libvlc_video_output_cleanup_cb  cleanupDeviceCb;
    } external;

#ifdef HAVE_DXGI_DEBUG
    dxgi_debug_handle_t                 dxgi_debug;
#endif
    d3d11_decoder_device_t              dec_device;
} d3d11_decoder_device;

void D3D11_ReleaseDevice(d3d11_decoder_device_t *dev_sys)
{
    d3d11_decoder_device *sys = container_of(dev_sys, d3d11_decoder_device, dec_device);
    d3d11_device_t *d3d_dev = &dev_sys->d3d_dev;
    if (d3d_dev->d3dcontext)
    {
        d3d_dev->d3dcontext->Flush();
        d3d_dev->d3dcontext->Release();
        d3d_dev->d3dcontext = NULL;
    }
    if (d3d_dev->d3ddevice)
    {
        d3d_dev->d3ddevice->Release();
        d3d_dev->d3ddevice = NULL;
    }
    if( d3d_dev->mutex_owner && d3d_dev->context_mutex != INVALID_HANDLE_VALUE )
    {
        CloseHandle( d3d_dev->context_mutex );
        d3d_dev->context_mutex = INVALID_HANDLE_VALUE;
    }

    if ( sys->external.cleanupDeviceCb )
        sys->external.cleanupDeviceCb( sys->external.opaque );

#ifdef HAVE_DXGI_DEBUG
    sys->dxgi_debug.LogResource();
    sys->dxgi_debug.Release();
#endif
}

static HRESULT D3D11_CreateDeviceExternal(vlc_object_t *obj, ID3D11DeviceContext *d3d11ctx,
                                          HANDLE context_lock, d3d11_device_t *out)
{
    if (unlikely(d3d11ctx == NULL))
    {
        msg_Err(obj, "missing external ID3D11DeviceContext");
        return E_FAIL;
    }

    HRESULT hr;
    d3d11ctx->GetDevice(&out->d3ddevice );

    if (!(out->d3ddevice->GetCreationFlags() & D3D11_CREATE_DEVICE_VIDEO_SUPPORT))
    {
        msg_Warn(obj, "the provided D3D11 device doesn't support decoding");
    }

    IDXGIAdapter *pAdapter = D3D11DeviceAdapter(out->d3ddevice);
    if (unlikely(!pAdapter))
    {
        msg_Warn(obj, "can't get adapter from device %p", (void*)out->d3ddevice);
        out->d3ddevice->Release();
        out->d3ddevice = NULL;
        return E_FAIL;
    }
    hr = pAdapter->GetDesc(&out->adapterDesc);
    if (FAILED(hr))
        msg_Warn(obj, "can't get adapter description");

    d3d11ctx ->AddRef();
    out->d3dcontext = d3d11ctx;
    out->mutex_owner = false;
    out->feature_level = out->d3ddevice ->GetFeatureLevel();

    out->context_mutex = context_lock;
    if (context_lock == NULL || context_lock == INVALID_HANDLE_VALUE)
    {
        msg_Warn(obj, "external ID3D11DeviceContext mutex not provided, using internal one");
        out->mutex_owner = true;
        out->context_mutex = CreateMutexEx( NULL, NULL, 0, SYNCHRONIZE );
    }

    D3D11_GetDriverVersion(obj, out, pAdapter);
    pAdapter->Release();
    return S_OK;
}

static HRESULT CreateDevice(vlc_object_t *obj,
                            IDXGIAdapter *adapter,
                            bool hw_decoding, d3d11_device_t *out)
{
    HRESULT hr = E_NOTIMPL;
    UINT creationFlags = 0;

    if (hw_decoding || !obj->force)
        creationFlags |= D3D11_CREATE_DEVICE_VIDEO_SUPPORT;

#if !defined(NDEBUG) && !defined(BUILD_FOR_UAP)
    if (IsDebuggerPresent())
    {
        HINSTANCE sdklayer_dll = LoadLibraryExA("d3d11_1sdklayers.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32);
        if (sdklayer_dll) {
            creationFlags |= D3D11_CREATE_DEVICE_DEBUG;
            FreeLibrary(sdklayer_dll);
        }
    }
#endif

    static const D3D_DRIVER_TYPE driverAttempts[] = {
        D3D_DRIVER_TYPE_HARDWARE,
#if 0 /* ifndef NDEBUG */
        D3D_DRIVER_TYPE_REFERENCE,
#endif
    };

    static D3D_FEATURE_LEVEL D3D11_features[] = {
        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
    };

    for (UINT driver = 0; driver < (adapter ? 1 : ARRAY_SIZE(driverAttempts)); driver++) {
        hr = D3D11CreateDevice(adapter, adapter ? D3D_DRIVER_TYPE_UNKNOWN : driverAttempts[driver],
                               NULL, creationFlags,
                               D3D11_features, ARRAY_SIZE(D3D11_features), D3D11_SDK_VERSION,
                               &out->d3ddevice, &out->feature_level, &out->d3dcontext);
        if (SUCCEEDED(hr)) {
            msg_Dbg(obj, "Created the D3D11 device type %d level %x (flags %08x).",
                    driverAttempts[driver], out->feature_level, creationFlags);
            if (adapter != NULL)
            {
                hr = adapter->GetDesc(&out->adapterDesc);
                D3D11_GetDriverVersion( obj, out, adapter );
            }
            else
            {
                IDXGIAdapter *adap = D3D11DeviceAdapter(out->d3ddevice);
                if (adap == NULL)
                    hr = E_FAIL;
                else
                {
                    hr = adap->GetDesc(&out->adapterDesc);
                    D3D11_GetDriverVersion( obj, out, adap );
                    adap->Release();
                }
            }
            if (FAILED(hr))
                msg_Warn(obj, "can't get adapter description");

            /* we can work with legacy levels but only if forced */
            if ( obj->force || out->feature_level >= D3D_FEATURE_LEVEL_11_0 )
                break;
            msg_Warn(obj, "Incompatible feature level %x", out->feature_level);
            out->d3dcontext->Release();
            out->d3ddevice->Release();
            out->d3dcontext = NULL;
            out->d3ddevice = NULL;
            hr = E_NOTIMPL;
        }
    }

    if (hw_decoding && SUCCEEDED(hr))
    {
        out->context_mutex = CreateMutexEx( NULL, NULL, 0, SYNCHRONIZE );
        out->mutex_owner = true;
    }
    else
        out->context_mutex = INVALID_HANDLE_VALUE;

    return hr;
}

d3d11_decoder_device_t *(D3D11_CreateDevice)(vlc_object_t *obj,
                                      IDXGIAdapter *adapter,
                                      bool hw_decoding, bool forced)
{
    d3d11_decoder_device *sys = static_cast<d3d11_decoder_device*>(vlc_obj_malloc(obj, sizeof(*sys)));
    if (unlikely(sys==NULL))
        return NULL;

#ifdef HAVE_DXGI_DEBUG
    sys->dxgi_debug.Init();
#endif

    sys->external.cleanupDeviceCb = NULL;
    HRESULT hr = E_FAIL;
    {
        libvlc_video_engine_t engineType = (libvlc_video_engine_t)var_InheritInteger( obj, "vout-cb-type" );
        libvlc_video_output_setup_cb setupDeviceCb = NULL;
        if (engineType == libvlc_video_engine_d3d11)
            setupDeviceCb = (libvlc_video_output_setup_cb)var_InheritAddress( obj, "vout-cb-setup" );
        if ( setupDeviceCb != NULL)
        {
            /* decoder device coming from the external app */
            sys->external.opaque          = var_InheritAddress( obj, "vout-cb-opaque" );
            sys->external.cleanupDeviceCb = (libvlc_video_output_cleanup_cb)var_InheritAddress( obj, "vout-cb-cleanup" );
            libvlc_video_setup_device_cfg_t cfg = {
                .hardware_decoding = true, /* always favor hardware decoding */
            };
            libvlc_video_setup_device_info_t out{};
            if (!setupDeviceCb( &sys->external.opaque, &cfg, &out ))
            {
                if (sys->external.cleanupDeviceCb)
                    sys->external.cleanupDeviceCb( sys->external.opaque );
                msg_Err(obj, "Failed to setup external D3D11 device");
                goto error;
            }
            hr = D3D11_CreateDeviceExternal(obj, static_cast<ID3D11DeviceContext*>(out.u.d3d11.device_context), out.u.d3d11.context_mutex,
                                            &sys->dec_device.d3d_dev);
        }
        else if ( engineType == libvlc_video_engine_disable ||
                  engineType == libvlc_video_engine_d3d11 )
        {
            /* internal decoder device */
#if !BUILD_FOR_UAP
            if (!forced)
            {
                /* Allow using D3D11 automatically starting from Windows 8.1 */
                bool isWin81OrGreater = false;
                HMODULE hKernel32 = GetModuleHandle(TEXT("kernel32.dll"));
                if (likely(hKernel32 != NULL))
                    isWin81OrGreater = GetProcAddress(hKernel32, "IsProcessCritical") != NULL;
                if (!isWin81OrGreater)
                {
                    msg_Dbg(obj, "D3D11 not forced on Win7/8");
                    goto error;
                }
            }
#endif

            hr = CreateDevice( obj, adapter, hw_decoding, &sys->dec_device.d3d_dev );
        }
        else
        {
            msg_Dbg(obj, "Unsupported engine type %d", engineType);
            goto error;
        }
    }

error:
    if (FAILED(hr))
    {
#ifdef HAVE_DXGI_DEBUG
        sys->dxgi_debug.LogResource();
        sys->dxgi_debug.Release();
#endif
        vlc_obj_free( obj, sys );
        return NULL;
    }
    return &sys->dec_device;
}

IDXGIAdapter *D3D11DeviceAdapter(ID3D11Device *d3ddev)
{
    IDXGIDevice *pDXGIDevice;
    HRESULT hr = d3ddev->QueryInterface(IID_GRAPHICS_PPV_ARGS(&pDXGIDevice));
    if (FAILED(hr)) {
        return NULL;
    }

    IDXGIAdapter *p_adapter;
    hr = pDXGIDevice->GetAdapter(&p_adapter);
    pDXGIDevice->Release();
    if (FAILED(hr)) {
        return NULL;
    }
    return p_adapter;
}

bool isXboxHardware([[maybe_unused]] const d3d11_device_t *d3ddev)
{
    bool result = false;

#if BUILD_FOR_UAP
    if (d3ddev->adapterDesc.VendorId == 0 &&
        d3ddev->adapterDesc.DeviceId == 0 &&
        !wcscmp(L"ROOT\\SraKmd\\0000", d3ddev->adapterDesc.Description))
        result = true;
#endif

    return result;
}

/**
 * Performs a check on each value of the WDDM version. Any value that is OK will
 * consider the driver valid (OR on each value)
 */
int D3D11CheckDriverVersion(const d3d11_device_t *d3d_dev, UINT vendorId, const struct wddm_version *min_ver)
{
    if (vendorId && d3d_dev->adapterDesc.VendorId != vendorId)
        return VLC_SUCCESS;

    if (min_ver->wddm)
    {
        if (d3d_dev->WDDM.wddm > min_ver->wddm)
            return VLC_SUCCESS;
        if (d3d_dev->WDDM.wddm != min_ver->wddm)
            return VLC_EGENERIC;
    }
    if (min_ver->d3d_features)
    {
        if (d3d_dev->WDDM.d3d_features > min_ver->d3d_features)
            return VLC_SUCCESS;
        if (d3d_dev->WDDM.d3d_features != min_ver->d3d_features)
            return VLC_EGENERIC;
    }
    if (min_ver->revision)
    {
        if (d3d_dev->WDDM.revision > min_ver->revision)
            return VLC_SUCCESS;
        if (d3d_dev->WDDM.revision != min_ver->revision)
            return VLC_EGENERIC;
    }
    if (min_ver->build)
    {
        if (d3d_dev->WDDM.build > min_ver->build)
            return VLC_SUCCESS;
        if (d3d_dev->WDDM.build != min_ver->build)
            return VLC_EGENERIC;
    }
    return VLC_SUCCESS;
}

/* test formats that should work but sometimes have issues on some platforms */
static bool CanReallyUseFormat(vlc_object_t *obj, d3d11_device_t *d3d_dev,
                               vlc_fourcc_t i_chroma, DXGI_FORMAT dxgi)
{
    bool result = true;
    if (dxgi == DXGI_FORMAT_UNKNOWN)
        return true;

    if (is_d3d11_opaque(i_chroma))
        return true;

    ID3D11Texture2D *texture = NULL;
    D3D11_TEXTURE2D_DESC texDesc;
    ZeroMemory(&texDesc, sizeof(texDesc));
    texDesc.MipLevels = 1;
    texDesc.SampleDesc.Count = 1;
    texDesc.MiscFlags = 0; //D3D11_RESOURCE_MISC_SHARED;
    texDesc.BindFlags = D3D11_BIND_SHADER_RESOURCE;
    texDesc.Usage = D3D11_USAGE_DYNAMIC;
    texDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
    texDesc.ArraySize = 1;
    texDesc.Format = dxgi;
    texDesc.Height = 144;
    texDesc.Width = 176;
    HRESULT hr = d3d_dev->d3ddevice->CreateTexture2D( &texDesc, NULL, &texture );
    if (FAILED(hr))
    {
        msg_Dbg(obj, "cannot allocate a writable texture type %s. (hr=0x%lX)", DxgiFormatToStr(dxgi), hr);
        return false;
    }

    D3D11_MAPPED_SUBRESOURCE mappedResource;
    hr = d3d_dev->d3dcontext->Map(texture, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource);
    if (FAILED(hr))
    {
        msg_Err(obj, "The texture type %s cannot be mapped. (hr=0x%lX)", DxgiFormatToStr(dxgi), hr);
        result = false;
        goto done;
    }
    d3d_dev->d3dcontext->Unmap(texture, 0);

    if (dxgi == DXGI_FORMAT_YUY2)
    {
        const vlc_chroma_description_t *p_chroma_desc = vlc_fourcc_GetChromaDescription( i_chroma );
        if( !p_chroma_desc )
        {
            msg_Err(obj, "No pixel format for %4.4s", (const char*)&i_chroma);
            result = false;
            goto done;
        }

        if (mappedResource.RowPitch >= 2 * (texDesc.Width * p_chroma_desc->p[0].w.num / p_chroma_desc->p[0].w.den * p_chroma_desc->pixel_size))
        {
            msg_Err(obj, "Bogus %4.4s pitch detected type %s. %d should be %d", (const char*)&i_chroma,
                          DxgiFormatToStr(dxgi), mappedResource.RowPitch,
                          (texDesc.Width * p_chroma_desc->p[0].w.num / p_chroma_desc->p[0].w.den * p_chroma_desc->pixel_size));
            result = false;
            goto done;
        }

    }
done:
    texture->Release();

    return result;
}

bool D3D11_DeviceSupportsFormat(d3d11_device_t *d3d_dev, DXGI_FORMAT format, UINT supportFlags)
{
    UINT i_formatSupport;
    return SUCCEEDED( d3d_dev->d3ddevice->CheckFormatSupport(format,
                                                      &i_formatSupport) )
            && ( i_formatSupport & supportFlags ) == supportFlags;
}


const d3d_format_t *(FindD3D11Format)(vlc_object_t *o,
                                    d3d11_device_t *d3d_dev,
                                    vlc_fourcc_t i_src_chroma,
                                    int rgb_yuv,
                                    uint8_t bits_per_channel,
                                    uint8_t widthDenominator,
                                    uint8_t heightDenominator,
                                    int alpha_bits,
                                    int cpu_gpu,
                                    UINT supportFlags)
{
    supportFlags |= D3D11_FORMAT_SUPPORT_TEXTURE2D;
    for (const d3d_format_t *output_format = DxgiGetRenderFormatList();
         output_format->name != NULL; ++output_format)
    {
        if (i_src_chroma && i_src_chroma != output_format->fourcc)
            continue;
        if (bits_per_channel && bits_per_channel > output_format->bitsPerChannel)
            continue;
        int cpu_gpu_fmt = is_d3d11_opaque(output_format->fourcc) ? DXGI_CHROMA_GPU : DXGI_CHROMA_CPU;
        if ((cpu_gpu & cpu_gpu_fmt)==0)
            continue;
        int format = vlc_fourcc_IsYUV(output_format->fourcc) ? DXGI_YUV_FORMAT : DXGI_RGB_FORMAT;
        if ((rgb_yuv & format)==0)
            continue;
        if (widthDenominator && widthDenominator < output_format->widthDenominator)
            continue;
        if (heightDenominator && heightDenominator < output_format->heightDenominator)
            continue;
        if (alpha_bits > 0 && output_format->bitsForAlpha < alpha_bits)
            continue;
        if (alpha_bits == 0 && output_format->bitsForAlpha != 0)
            continue;

        DXGI_FORMAT textureFormat;
        if (output_format->formatTexture == DXGI_FORMAT_UNKNOWN)
            textureFormat = output_format->resourceFormat[0];
        else
            textureFormat = output_format->formatTexture;

        if( D3D11_DeviceSupportsFormat( d3d_dev, textureFormat, supportFlags ) &&
            CanReallyUseFormat(o, d3d_dev, output_format->fourcc, output_format->formatTexture) )
            return output_format;
    }
    return NULL;
}

void D3D11_PictureAttach(picture_t *pic, ID3D11Texture2D *slicedTexture, const d3d_format_t *cfg)
{
    d3d11_pic_context *pic_ctx = D3D11_PICCONTEXT_FROM_PICCTX(pic->context);
    D3D11_TEXTURE2D_DESC texDesc;
    slicedTexture->GetDesc(&texDesc);

    if (texDesc.CPUAccessFlags != 0)
    {
        const video_format_t *fmt = &pic->format;

        const vlc_chroma_description_t *p_chroma_desc = vlc_fourcc_GetChromaDescription( fmt->i_chroma );
        if( !p_chroma_desc )
            return;

        for( unsigned i = 0; i < p_chroma_desc->plane_count; i++ )
        {
            plane_t *p = &pic->p[i];

            p->i_lines         = fmt->i_height * p_chroma_desc->p[i].h.num / p_chroma_desc->p[i].h.den;
            p->i_visible_lines = fmt->i_visible_height * p_chroma_desc->p[i].h.num / p_chroma_desc->p[i].h.den;
            p->i_pitch         = fmt->i_width * p_chroma_desc->p[i].w.num / p_chroma_desc->p[i].w.den * p_chroma_desc->pixel_size;
            p->i_visible_pitch = fmt->i_visible_width * p_chroma_desc->p[i].w.num / p_chroma_desc->p[i].w.den * p_chroma_desc->pixel_size;
            p->i_pixel_pitch   = p_chroma_desc->pixel_size;
        }
    }

    for (unsigned plane = 0; plane < DXGI_MAX_SHADER_VIEW; plane++)
    {
        if (!cfg->resourceFormat[plane])
        {
            pic_ctx->picsys.texture[plane] = NULL;
        }
        else
        {
            pic_ctx->picsys.texture[plane] = slicedTexture;
            slicedTexture->AddRef();
        }
    }
}

#undef AllocateTextures
int AllocateTextures( vlc_object_t *obj, d3d11_device_t *d3d_dev,
                      const d3d_format_t *cfg, const video_format_t *fmt, bool shared,
                      ID3D11Texture2D *textures[],
                      plane_t out_planes[] )
{
    plane_t planes[PICTURE_PLANE_MAX];
    unsigned plane = 0, plane_count;
    HRESULT hr;
    ID3D11Texture2D *slicedTexture = NULL;
    D3D11_TEXTURE2D_DESC texDesc;
    ZeroMemory(&texDesc, sizeof(texDesc));
    texDesc.MipLevels = 1;
    texDesc.SampleDesc.Count = 1;
    if (shared)
        texDesc.MiscFlags = D3D11_RESOURCE_MISC_SHARED | D3D11_RESOURCE_MISC_SHARED_NTHANDLE;
    texDesc.BindFlags = D3D11_BIND_SHADER_RESOURCE;
    texDesc.ArraySize = 1;
    if (is_d3d11_opaque(fmt->i_chroma)) {
        texDesc.BindFlags |= D3D11_BIND_RENDER_TARGET;
        texDesc.Usage = D3D11_USAGE_DEFAULT;
        texDesc.CPUAccessFlags = 0;
    } else {
        texDesc.Usage = D3D11_USAGE_DYNAMIC;
        texDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
    }

    const vlc_chroma_description_t *p_chroma_desc = vlc_fourcc_GetChromaDescription( fmt->i_chroma );
    if( !p_chroma_desc )
        return VLC_EGENERIC;

    if (cfg->formatTexture == DXGI_FORMAT_UNKNOWN) {
        if (p_chroma_desc->plane_count == 0)
        {
            msg_Dbg(obj, "failed to get the pixel format planes for %4.4s", (char *)&fmt->i_chroma);
            return VLC_EGENERIC;
        }
        assert(p_chroma_desc->plane_count <= DXGI_MAX_SHADER_VIEW);
        plane_count = p_chroma_desc->plane_count;

        texDesc.Format = cfg->resourceFormat[0];
        assert(cfg->resourceFormat[1] == cfg->resourceFormat[0]);
        assert(cfg->resourceFormat[2] == cfg->resourceFormat[0]);

    } else {
        plane_count = __MAX(1, p_chroma_desc->plane_count);
        texDesc.Format = cfg->formatTexture;
        texDesc.Height = fmt->i_height;
        texDesc.Width = fmt->i_width;

        hr = d3d_dev->d3ddevice->CreateTexture2D( &texDesc, NULL, &slicedTexture );
        if (FAILED(hr)) {
            msg_Err(obj, "CreateTexture2D failed. (hr=0x%lX)", hr);
            goto error;
        }
    }
    for( unsigned i = 0; i < p_chroma_desc->plane_count; i++ )
    {
        plane_t *p = &planes[i];

        p->i_lines         = fmt->i_height * p_chroma_desc->p[i].h.num / p_chroma_desc->p[i].h.den;
        p->i_visible_lines = fmt->i_visible_height * p_chroma_desc->p[i].h.num / p_chroma_desc->p[i].h.den;
        p->i_pitch         = fmt->i_width * p_chroma_desc->p[i].w.num / p_chroma_desc->p[i].w.den * p_chroma_desc->pixel_size;
        p->i_visible_pitch = fmt->i_visible_width * p_chroma_desc->p[i].w.num / p_chroma_desc->p[i].w.den * p_chroma_desc->pixel_size;
        p->i_pixel_pitch   = p_chroma_desc->pixel_size;
    }

    for (plane = 0; plane < plane_count; plane++)
    {
        if (slicedTexture) {
            textures[plane] = slicedTexture;
            slicedTexture->AddRef();
        } else {
            texDesc.Height = planes[plane].i_lines;
            texDesc.Width  = planes[plane].i_pitch / p_chroma_desc->pixel_size;
            hr = d3d_dev->d3ddevice->CreateTexture2D( &texDesc, NULL, &textures[plane] );
            if (FAILED(hr)) {
                msg_Err(obj, "CreateTexture2D failed for plane %d. (hr=0x%lX)", plane, hr);
                goto error;
            }
        }
    }
    if (out_planes)
        for (plane = 0; plane < p_chroma_desc->plane_count; plane++)
        {
            out_planes[plane] = planes[plane];
        }
    for (; plane < DXGI_MAX_SHADER_VIEW; plane++) {
        if (!cfg->resourceFormat[plane])
            textures[plane] = NULL;
        else
        {
            textures[plane] = textures[0];
            textures[plane]->AddRef();
        }
    }

    if (slicedTexture)
        slicedTexture->Release();
    return VLC_SUCCESS;
error:
    if (slicedTexture)
        slicedTexture->Release();
    for (unsigned j = 0; j < plane; j++)
        textures[j]->Release();
    return VLC_EGENERIC;
}

void D3D11_LogResources([[maybe_unused]] d3d11_decoder_device_t *dev_sys)
{
#ifdef HAVE_DXGI_DEBUG
    d3d11_decoder_device *sys = container_of(dev_sys, d3d11_decoder_device, dec_device);
    sys->dxgi_debug.LogResource();
#endif
}

const struct vlc_video_context_operations d3d11_vctx_ops = {
    NULL,
};

vlc_video_context *D3D11CreateVideoContext(vlc_decoder_device *dec_dev, DXGI_FORMAT vctx_fmt, DXGI_FORMAT alpha)
{
    vlc_video_context *vctx = vlc_video_context_Create( dec_dev, VLC_VIDEO_CONTEXT_D3D11VA,
                                          sizeof(d3d11_video_context_t), &d3d11_vctx_ops );
    if (unlikely(vctx == NULL))
        return NULL;

    d3d11_video_context_t *priv = GetD3D11ContextPrivate(vctx);
    priv->format = vctx_fmt;
    priv->secondary = alpha;
    return vctx;
}

void d3d11_pic_context_destroy(picture_context_t *ctx)
{
    d3d11_pic_context *pic_ctx = D3D11_PICCONTEXT_FROM_PICCTX(ctx);
    ReleaseD3D11PictureSys(&pic_ctx->picsys);
    if (pic_ctx->picsys.sharedHandle != INVALID_HANDLE_VALUE && pic_ctx->picsys.ownHandle)
        CloseHandle(pic_ctx->picsys.sharedHandle);
    free(pic_ctx);
}

picture_context_t *d3d11_pic_context_copy(picture_context_t *ctx)
{
    d3d11_pic_context *pic_ctx = static_cast<d3d11_pic_context*>(calloc(1, sizeof(*pic_ctx)));
    if (unlikely(pic_ctx==NULL))
        return NULL;
    *pic_ctx = *D3D11_PICCONTEXT_FROM_PICCTX(ctx);
    vlc_video_context_Hold(pic_ctx->s.vctx);
    AcquireD3D11PictureSys(&pic_ctx->picsys);
    return &pic_ctx->s;
}

int D3D11_PictureFill(vlc_object_t *obj, picture_t *pic,
                      vlc_video_context *vctx_out,
                      bool shared, const d3d_format_t *cfg)
{
    if (unlikely(cfg == NULL))
        return VLC_EINVAL;

    d3d11_pic_context *pic_ctx = static_cast<d3d11_pic_context*>(calloc(1, sizeof(*pic_ctx)));
    if (unlikely(pic_ctx == NULL))
        return VLC_ENOMEM;
    pic_ctx->picsys.sharedHandle = INVALID_HANDLE_VALUE;

    d3d11_decoder_device_t *dev_sys = GetD3D11OpaqueContext(vctx_out);
    if (AllocateTextures(obj, &dev_sys->d3d_dev, cfg,
                         &pic->format, shared, pic_ctx->picsys.texture, NULL) != VLC_SUCCESS)
    {
        free(pic_ctx);
        return VLC_EGENERIC;
    }

    D3D11_AllocateResourceView(vlc_object_logger(obj), dev_sys->d3d_dev.d3ddevice, cfg, pic_ctx->picsys.texture, 0, pic_ctx->picsys.renderSrc);

    if (shared)
    {
        HRESULT hr;
        IDXGIResource1 *sharedResource;
        hr = pic_ctx->picsys.texture[0]->QueryInterface(IID_GRAPHICS_PPV_ARGS(&sharedResource));
        if (likely(SUCCEEDED(hr)))
        {
            sharedResource->CreateSharedHandle(NULL,
                                              DXGI_SHARED_RESOURCE_READ/*|DXGI_SHARED_RESOURCE_WRITE*/,
                                              NULL, &pic_ctx->picsys.sharedHandle);
            pic_ctx->picsys.ownHandle = true;
            sharedResource->Release();
        }
    }

    pic_ctx->s = (picture_context_t) {
        d3d11_pic_context_destroy, d3d11_pic_context_copy,
        vlc_video_context_Hold(vctx_out),
    };
    pic->context = &pic_ctx->s;
    return VLC_SUCCESS;
}

picture_t *D3D11_AllocPicture(vlc_object_t *obj,
                              const video_format_t *fmt, vlc_video_context *vctx_out,
                              bool shared, const d3d_format_t *cfg)
{
    picture_t *pic = picture_NewFromFormat( fmt );
    if (unlikely(pic == NULL))
        return NULL;

    if (D3D11_PictureFill(obj, pic, vctx_out, shared, cfg) != VLC_SUCCESS)
    {
        picture_Release(pic);
        return NULL;
    }

    return pic;
}
