/*****************************************************************************
 * d3d11_fmt.c : 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.
 *****************************************************************************/

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

#if _WIN32_WINNT < 0x0601 // _WIN32_WINNT_WIN7
# undef _WIN32_WINNT
# define _WIN32_WINNT _WIN32_WINNT_WIN7
#endif

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

#define COBJMACROS
#include <d3d11.h>
#include <assert.h>
#if !defined(NDEBUG) && defined(HAVE_DXGIDEBUG_H)
# include <initguid.h>
# include <dxgidebug.h>
#endif

#include "d3d11_fmt.h"

#include "../codec/avcodec/va_surface.h"

picture_sys_t *ActivePictureSys(picture_t *p_pic)
{
    struct va_pic_context *pic_ctx = (struct va_pic_context*)p_pic->context;
    return pic_ctx ? &pic_ctx->picsys : p_pic->p_sys;
}

void AcquirePictureSys(picture_sys_t *p_sys)
{
    for (int i=0; i<D3D11_MAX_SHADER_VIEW; i++) {
        if (p_sys->resourceView[i])
            ID3D11ShaderResourceView_AddRef(p_sys->resourceView[i]);
        if (p_sys->texture[i])
            ID3D11Texture2D_AddRef(p_sys->texture[i]);
    }
    if (p_sys->context)
        ID3D11DeviceContext_AddRef(p_sys->context);
    if (p_sys->decoder)
        ID3D11VideoDecoderOutputView_AddRef(p_sys->decoder);
    if (p_sys->processorInput)
        ID3D11VideoProcessorInputView_AddRef(p_sys->processorInput);
    if (p_sys->processorOutput)
        ID3D11VideoProcessorOutputView_AddRef(p_sys->processorOutput);
}

void ReleasePictureSys(picture_sys_t *p_sys)
{
    for (int i=0; i<D3D11_MAX_SHADER_VIEW; i++) {
        if (p_sys->resourceView[i])
            ID3D11ShaderResourceView_Release(p_sys->resourceView[i]);
        if (p_sys->texture[i])
            ID3D11Texture2D_Release(p_sys->texture[i]);
    }
    if (p_sys->context)
        ID3D11DeviceContext_Release(p_sys->context);
    if (p_sys->decoder)
        ID3D11VideoDecoderOutputView_Release(p_sys->decoder);
    if (p_sys->processorInput)
        ID3D11VideoProcessorInputView_Release(p_sys->processorInput);
    if (p_sys->processorOutput)
        ID3D11VideoProcessorOutputView_Release(p_sys->processorOutput);
}

/* map texture planes to resource views */
#undef D3D11_AllocateShaderView
int D3D11_AllocateShaderView(vlc_object_t *obj, ID3D11Device *d3ddevice,
                              const d3d_format_t *format,
                              ID3D11Texture2D *p_texture[D3D11_MAX_SHADER_VIEW], UINT slice_index,
                              ID3D11ShaderResourceView *resourceView[D3D11_MAX_SHADER_VIEW])
{
    HRESULT hr;
    int i;
    D3D11_SHADER_RESOURCE_VIEW_DESC resviewDesc = { 0 };
    D3D11_TEXTURE2D_DESC texDesc;
    ID3D11Texture2D_GetDesc(p_texture[0], &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<D3D11_MAX_SHADER_VIEW; i++)
    {
        resviewDesc.Format = format->resourceFormat[i];
        if (resviewDesc.Format == DXGI_FORMAT_UNKNOWN)
            resourceView[i] = NULL;
        else
        {
            hr = ID3D11Device_CreateShaderResourceView(d3ddevice, (ID3D11Resource*)p_texture[i], &resviewDesc, &resourceView[i]);
            if (FAILED(hr)) {
                msg_Err(obj, "Could not Create the Texture ResourceView %d slice %d. (hr=0x%lX)", i, slice_index, hr);
                break;
            }
        }
    }

    if (i != D3D11_MAX_SHADER_VIEW)
    {
        while (--i >= 0)
        {
            ID3D11ShaderResourceView_Release(resourceView[i]);
            resourceView[i] = NULL;
        }
        return VLC_EGENERIC;
    }

    return VLC_SUCCESS;
}

#undef D3D11_GetDriverVersion

#if !VLC_WINSTORE_APP
static HKEY GetAdapterRegistry(vlc_object_t *obj, DXGI_ADAPTER_DESC *adapterDesc)
{
    HKEY hDisplayKey, hKey;
    CHAR szData[256], lookup[256];
    DWORD len = 256;
    LSTATUS ret;

    ret = RegOpenKeyExA(HKEY_LOCAL_MACHINE, "SYSTEM\\CurrentControlSet\\Control\\Class\\{4d36e968-e325-11ce-bfc1-08002be10318}", 0, KEY_READ, &hDisplayKey);
    if ( ret != ERROR_SUCCESS )
    {
        msg_Warn(obj, "failed to read the Display Adapter registry key (%ld)", ret);
        return NULL;
    }

    char DisplayNum[16];
    _snprintf(lookup, 256, "pci\\ven_%04x&dev_%04x", adapterDesc->VendorId, adapterDesc->DeviceId);
    for (DWORD k=0;; k++)
    {
        ret = RegEnumKeyA(hDisplayKey, k, DisplayNum, ARRAY_SIZE(DisplayNum));
        if (ret != ERROR_SUCCESS)
            break;

        ret = RegOpenKeyExA(hDisplayKey, DisplayNum, 0, KEY_READ, &hKey);
        if ( ret != ERROR_SUCCESS )
        {
            msg_Warn(obj, "failed to read the %s Display Adapter registry key (%ld)", DisplayNum, ret);
            continue;
        }

        len = sizeof(szData);
        ret = RegQueryValueExA( hKey, "MatchingDeviceId", NULL, NULL, (LPBYTE) &szData, &len );
        if ( ret == ERROR_SUCCESS ) {
            if (_strnicmp(lookup, szData, strlen(lookup)) == 0)
            {
                RegCloseKey(hDisplayKey);
                return hKey;
            }
            msg_Dbg(obj, "different %s device %s vs %s", DisplayNum, lookup, szData);
        }
        else
            msg_Warn(obj, "failed to get the %s MatchingDeviceId (%ld)", DisplayNum, ret);

        RegCloseKey(hKey);
    }
    RegCloseKey(hDisplayKey);

    return NULL;
}

static void D3D11_GetSystemDriver(vlc_object_t *obj, d3d11_device_t *d3d_dev)
{
    memset(&d3d_dev->WDDM, 0, sizeof(d3d_dev->WDDM));

    LONG err = ERROR_ACCESS_DENIED;
    CHAR szData[256];
    DWORD len = 256;
    HKEY hKey = GetAdapterRegistry(obj, &d3d_dev->adapterDesc);
    if (hKey == NULL)
    {
        msg_Warn(obj, "can't find adapter in registry");
        return;
    }

    err = RegQueryValueExA( hKey, "DriverVersion", NULL, NULL, (LPBYTE) &szData, &len );
    RegCloseKey(hKey);

    if (err != ERROR_SUCCESS )
    {
        msg_Warn(obj, "failed to read the adapter DriverVersion");
        return;
    }

    int wddm, d3d_features, revision, build;
    /* see https://msdn.microsoft.com/windows/hardware/commercialize/design/compatibility/device-graphics */
    if (sscanf(szData, "%d.%d.%d.%d", &wddm, &d3d_features, &revision, &build) != 4)
    {
        msg_Warn(obj, "the adapter DriverVersion '%s' doesn't match the expected format", szData);
        return;
    }
    d3d_dev->WDDM.wddm         = wddm;
    d3d_dev->WDDM.d3d_features = d3d_features;
    d3d_dev->WDDM.revision     = revision;
    d3d_dev->WDDM.build        = build;
    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 */
        d3d_dev->WDDM.build += (revision - 100) * 1000;
    }
}
#else /* VLC_WINSTORE_APP */
static void D3D11_GetSystemDriver(vlc_object_t *obj, d3d11_device_t *d3d_dev)
{
    VLC_UNUSED(obj);
    VLC_UNUSED(d3d_dev);
    return;
}
#endif /* VLC_WINSTORE_APP */

void D3D11_GetDriverVersion(vlc_object_t *obj, d3d11_device_t *d3d_dev)
{
    memset(&d3d_dev->WDDM, 0, sizeof(d3d_dev->WDDM));

    LARGE_INTEGER driver = { 0 };
    HRESULT hr;
    IDXGIAdapter *pAdapter = D3D11DeviceAdapter(d3d_dev->d3ddevice);
    hr = IDXGIAdapter_CheckInterfaceSupport(pAdapter, &IID_IDXGIDevice, &driver);

    if (FAILED(hr))
    {
        msg_Dbg(obj, "failed to get interface version. (hr=0x%lX)", hr);
        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);
        D3D11_GetSystemDriver(obj, d3d_dev);
    }
    else
    {
        d3d_dev->WDDM.wddm         = HIWORD(driver.HighPart);
        d3d_dev->WDDM.d3d_features = LOWORD(driver.LowPart);
        d3d_dev->WDDM.revision     = HIWORD(driver.LowPart);
        d3d_dev->WDDM.build        = LOWORD(driver.LowPart);
    }
}

void D3D11_ReleaseDevice(d3d11_device_t *d3d_dev)
{
    if (d3d_dev->d3dcontext)
    {
        ID3D11DeviceContext_Flush(d3d_dev->d3dcontext);
        ID3D11DeviceContext_Release(d3d_dev->d3dcontext);
        d3d_dev->d3dcontext = NULL;
    }
    if (d3d_dev->d3ddevice)
    {
        ID3D11Device_Release(d3d_dev->d3ddevice);
        d3d_dev->d3ddevice = NULL;
    }
#if defined(HAVE_ID3D11VIDEODECODER)
    if( d3d_dev->owner && d3d_dev->context_mutex != INVALID_HANDLE_VALUE )
    {
        CloseHandle( d3d_dev->context_mutex );
        d3d_dev->context_mutex = INVALID_HANDLE_VALUE;
    }
#endif
}

#undef D3D11_CreateDevice
HRESULT D3D11_CreateDevice(vlc_object_t *obj, d3d11_handle_t *hd3d,
                           bool hw_decoding, d3d11_device_t *out)
{
#if !VLC_WINSTORE_APP
# define D3D11CreateDevice(args...)             pf_CreateDevice(args)
    /* */
    PFN_D3D11_CREATE_DEVICE pf_CreateDevice;
    pf_CreateDevice = (void *)GetProcAddress(hd3d->hdll, "D3D11CreateDevice");
    if (!pf_CreateDevice) {
        msg_Err(obj, "Cannot locate reference to D3D11CreateDevice ABI in DLL");
        return E_NOINTERFACE;
    }
#endif /* VLC_WINSTORE_APP */

    HRESULT hr = E_NOTIMPL;
    UINT creationFlags = 0;

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

#if !defined(NDEBUG)
# if !VLC_WINSTORE_APP
    if (IsDebuggerPresent())
# endif /* VLC_WINSTORE_APP */
    {
        HINSTANCE sdklayer_dll = LoadLibrary(TEXT("d3d11_1sdklayers.dll"));
        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 < ARRAY_SIZE(driverAttempts); driver++) {
        hr = D3D11CreateDevice(NULL, 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.",
                    driverAttempts[driver], out->feature_level);
            {
                IDXGIAdapter *adap = D3D11DeviceAdapter(out->d3ddevice);
                if (adap == NULL)
                    hr = E_FAIL;
                else
                {
                    hr = IDXGIAdapter_GetDesc(adap, &out->adapterDesc);
                    IDXGIAdapter_Release(adap);
                }
            }
            if (hr)
                msg_Warn(obj, "can't get adapter description");

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

    if (SUCCEEDED(hr))
    {
#if defined(HAVE_ID3D11VIDEODECODER)
        out->context_mutex = CreateMutexEx( NULL, NULL, 0, SYNCHRONIZE );
        ID3D11DeviceContext_SetPrivateData( out->d3dcontext, &GUID_CONTEXT_MUTEX,
                                            sizeof( out->context_mutex ), &out->context_mutex );
#endif

        out->owner = true;
    }

    return hr;
}

IDXGIAdapter *D3D11DeviceAdapter(ID3D11Device *d3ddev)
{
    IDXGIDevice *pDXGIDevice = NULL;
    HRESULT hr = ID3D11Device_QueryInterface(d3ddev, &IID_IDXGIDevice, (void **)&pDXGIDevice);
    if (FAILED(hr)) {
        return NULL;
    }

    IDXGIAdapter *p_adapter;
    hr = IDXGIDevice_GetAdapter(pDXGIDevice, &p_adapter);
    IDXGIDevice_Release(pDXGIDevice);
    if (FAILED(hr)) {
        return NULL;
    }
    return p_adapter;
}

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

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

    return result;
}

static bool isNvidiaHardware(ID3D11Device *d3ddev)
{
    IDXGIAdapter *p_adapter = D3D11DeviceAdapter(d3ddev);
    if (!p_adapter)
        return false;

    DXGI_ADAPTER_DESC adapterDesc;
    if (FAILED(IDXGIAdapter_GetDesc(p_adapter, &adapterDesc)))
        adapterDesc.VendorId = 0;
    IDXGIAdapter_Release(p_adapter);

    return adapterDesc.VendorId == GPU_MANUFACTURER_NVIDIA;
}

bool CanUseVoutPool(d3d11_device_t *d3d_dev, UINT slices)
{
#if VLC_WINSTORE_APP
    /* Phones and the Xbox are memory constrained, rely on the d3d11va pool
     * which is always smaller, we still get direct rendering from the decoder */
    return false;
#else
    /* NVIDIA cards crash when calling CreateVideoDecoderOutputView
     * on more than 30 slices */
    return slices <= 30 || !isNvidiaHardware(d3d_dev->d3ddevice);
#endif
}

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;
        else 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;
        else 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;
        else 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;
        else 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 = ID3D11Device_CreateTexture2D( d3d_dev->d3ddevice, &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 = ID3D11DeviceContext_Map(d3d_dev->d3dcontext, (ID3D11Resource*)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;
    }
    ID3D11DeviceContext_Unmap(d3d_dev->d3dcontext, (ID3D11Resource*)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:
    ID3D11Texture2D_Release(texture);

    return result;
}

bool DeviceSupportsFormat(ID3D11Device *d3ddevice, DXGI_FORMAT format, UINT supportFlags)
{
    UINT i_formatSupport;
    return SUCCEEDED( ID3D11Device_CheckFormatSupport(d3ddevice, format,
                                                      &i_formatSupport) )
            && ( i_formatSupport & supportFlags ) == supportFlags;
}

#undef FindD3D11Format
const d3d_format_t *FindD3D11Format(vlc_object_t *o,
                                    d3d11_device_t *d3d_dev,
                                    vlc_fourcc_t i_src_chroma,
                                    bool rgb_only,
                                    uint8_t bits_per_channel,
                                    uint8_t widthDenominator,
                                    uint8_t heightDenominator,
                                    bool allow_opaque,
                                    UINT supportFlags)
{
    supportFlags |= D3D11_FORMAT_SUPPORT_TEXTURE2D;
    for (const d3d_format_t *output_format = GetRenderFormatList();
         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;
        if (!allow_opaque && is_d3d11_opaque(output_format->fourcc))
            continue;
        if (rgb_only && vlc_fourcc_IsYUV(output_format->fourcc))
            continue;
        if (widthDenominator && widthDenominator < output_format->widthDenominator)
            continue;
        if (heightDenominator && heightDenominator < output_format->heightDenominator)
            continue;

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

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

#undef AllocateTextures
int AllocateTextures( vlc_object_t *obj, d3d11_device_t *d3d_dev,
                      const d3d_format_t *cfg, const video_format_t *fmt,
                      bool for_decoder, bool shared,
                      unsigned pool_size, ID3D11Texture2D *textures[] )
{
    plane_t planes[PICTURE_PLANE_MAX];
    int plane, plane_count;
    HRESULT hr;
    ID3D11Texture2D *slicedTexture = 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;
    if (is_d3d11_opaque(fmt->i_chroma)) {
        if (for_decoder)
            texDesc.BindFlags |= D3D11_BIND_DECODER;
        texDesc.Usage = D3D11_USAGE_DEFAULT;
        texDesc.CPUAccessFlags = 0;
    } else {
        texDesc.Usage = D3D11_USAGE_DYNAMIC;
        texDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
    }
    texDesc.ArraySize = pool_size;
    if (shared)
        texDesc.MiscFlags |= D3D11_RESOURCE_MISC_SHARED | D3D11_RESOURCE_MISC_SHARED_NTHANDLE;

    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 <= D3D11_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]);

        for( int i = 0; i < 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;
        }
    } else {
        plane_count = 1;
        texDesc.Format = cfg->formatTexture;
        texDesc.Height = fmt->i_height;
        texDesc.Width = fmt->i_width;

        hr = ID3D11Device_CreateTexture2D( d3d_dev->d3ddevice, &texDesc, NULL, &slicedTexture );
        if (FAILED(hr)) {
            msg_Err(obj, "CreateTexture2D failed for the %d pool. (hr=0x%0lx)", pool_size, hr);
            goto error;
        }
    }

    for (unsigned picture_count = 0; picture_count < pool_size; picture_count++) {
        for (plane = 0; plane < plane_count; plane++)
        {
            if (slicedTexture) {
                textures[picture_count * D3D11_MAX_SHADER_VIEW + plane] = slicedTexture;
                ID3D11Texture2D_AddRef(slicedTexture);
            } else {
                texDesc.Height = fmt->i_height * p_chroma_desc->p[plane].h.num / p_chroma_desc->p[plane].h.den;
                texDesc.Width = fmt->i_width * p_chroma_desc->p[plane].w.num / p_chroma_desc->p[plane].w.den;
                hr = ID3D11Device_CreateTexture2D( d3d_dev->d3ddevice, &texDesc, NULL, &textures[picture_count * D3D11_MAX_SHADER_VIEW + plane] );
                if (FAILED(hr)) {
                    msg_Err(obj, "CreateTexture2D failed for the %d pool. (hr=0x%0lx)", pool_size, hr);
                    goto error;
                }
            }
        }
        for (; plane < D3D11_MAX_SHADER_VIEW; plane++) {
            if (!cfg->resourceFormat[plane])
                textures[picture_count * D3D11_MAX_SHADER_VIEW + plane] = NULL;
            else
            {
                textures[picture_count * D3D11_MAX_SHADER_VIEW + plane] = textures[picture_count * D3D11_MAX_SHADER_VIEW];
                ID3D11Texture2D_AddRef(textures[picture_count * D3D11_MAX_SHADER_VIEW + plane]);
            }
        }
    }

    if (slicedTexture)
        ID3D11Texture2D_Release(slicedTexture);
    return VLC_SUCCESS;
error:
    if (slicedTexture)
        ID3D11Texture2D_Release(slicedTexture);
    return VLC_EGENERIC;
}

#if !VLC_WINSTORE_APP
static HINSTANCE Direct3D11LoadShaderLibrary(void)
{
    HINSTANCE instance = NULL;
    /* d3dcompiler_47 is the latest on windows 8.1 */
    for (int i = 47; i > 41; --i) {
        TCHAR filename[19];
        _sntprintf(filename, 19, TEXT("D3DCOMPILER_%d.dll"), i);
        instance = LoadLibrary(filename);
        if (instance) break;
    }
    return instance;
}
#endif

#undef D3D11_Create
int D3D11_Create(vlc_object_t *obj, d3d11_handle_t *hd3d, bool with_shaders)
{
#if !VLC_WINSTORE_APP
#if !defined(NDEBUG) && defined(HAVE_DXGIDEBUG_H)
    hd3d->dxgidebug_dll = NULL;
#endif
    hd3d->hdll = LoadLibrary(TEXT("D3D11.DLL"));
    if (!hd3d->hdll)
    {
        msg_Warn(obj, "cannot load d3d11.dll, aborting");
        return VLC_EGENERIC;
    }

    if (with_shaders)
    {
        hd3d->compiler_dll = Direct3D11LoadShaderLibrary();
        if (!hd3d->compiler_dll) {
            msg_Err(obj, "cannot load d3dcompiler.dll, aborting");
            FreeLibrary(hd3d->hdll);
            return VLC_EGENERIC;
        }

        hd3d->OurD3DCompile = (void *)GetProcAddress(hd3d->compiler_dll, "D3DCompile");
        if (!hd3d->OurD3DCompile) {
            msg_Err(obj, "Cannot locate reference to D3DCompile in d3dcompiler DLL");
            FreeLibrary(hd3d->compiler_dll);
            FreeLibrary(hd3d->hdll);
            return VLC_EGENERIC;
        }
    }
# if !defined(NDEBUG) && defined(HAVE_DXGIDEBUG_H)
    hd3d->dxgidebug_dll = NULL;
    hd3d->pf_DXGIGetDebugInterface = NULL;
    if (IsDebuggerPresent())
    {
        hd3d->dxgidebug_dll = LoadLibrary(TEXT("DXGIDEBUG.DLL"));
        if (hd3d->dxgidebug_dll)
        {
            hd3d->pf_DXGIGetDebugInterface =
                    (void *)GetProcAddress(hd3d->dxgidebug_dll, "DXGIGetDebugInterface");
            if (unlikely(!hd3d->pf_DXGIGetDebugInterface))
            {
                FreeLibrary(hd3d->dxgidebug_dll);
                hd3d->dxgidebug_dll = NULL;
            }
        }
    }
# endif // !NDEBUG && HAVE_DXGIDEBUG_H
#endif
    return VLC_SUCCESS;
}

void D3D11_LogResources(d3d11_handle_t *hd3d)
{
#if !VLC_WINSTORE_APP
# if !defined(NDEBUG) && defined(HAVE_DXGIDEBUG_H)
    if (hd3d->pf_DXGIGetDebugInterface)
    {
        IDXGIDebug *pDXGIDebug;
        if (SUCCEEDED(hd3d->pf_DXGIGetDebugInterface(&IID_IDXGIDebug, (void**)&pDXGIDebug)))
        {
            IDXGIDebug_ReportLiveObjects(pDXGIDebug, DXGI_DEBUG_ALL, DXGI_DEBUG_RLO_ALL);
            IDXGIDebug_Release(pDXGIDebug);
        }
    }
# endif
#endif
}

void D3D11_Destroy(d3d11_handle_t *hd3d)
{
    D3D11_LogResources(hd3d);
#if !VLC_WINSTORE_APP
    if (hd3d->hdll)
        FreeLibrary(hd3d->hdll);

    if (hd3d->compiler_dll)
    {
        FreeLibrary(hd3d->compiler_dll);
        hd3d->compiler_dll = NULL;
    }
    hd3d->OurD3DCompile = NULL;

#if !defined(NDEBUG) && defined(HAVE_DXGIDEBUG_H)
    if (hd3d->dxgidebug_dll)
        FreeLibrary(hd3d->dxgidebug_dll);
#endif
#endif
}

#ifndef NDEBUG
#undef D3D11_LogProcessorSupport
void D3D11_LogProcessorSupport(vlc_object_t *o,
                               ID3D11VideoProcessorEnumerator *processorEnumerator)
{
    UINT flags;
    HRESULT hr;
    for (int format = 0; format < 188; format++) {
        hr = ID3D11VideoProcessorEnumerator_CheckVideoProcessorFormat(processorEnumerator, format, &flags);
        if (FAILED(hr))
            continue;
        const char *name = DxgiFormatToStr(format);
        const char *support = NULL;
        if ((flags & (D3D11_VIDEO_PROCESSOR_FORMAT_SUPPORT_INPUT|D3D11_VIDEO_PROCESSOR_FORMAT_SUPPORT_OUTPUT))
                 == (D3D11_VIDEO_PROCESSOR_FORMAT_SUPPORT_INPUT|D3D11_VIDEO_PROCESSOR_FORMAT_SUPPORT_OUTPUT))
            support = "input/output";
        else if (flags & D3D11_VIDEO_PROCESSOR_FORMAT_SUPPORT_INPUT)
            support = "input";
        else if (flags & D3D11_VIDEO_PROCESSOR_FORMAT_SUPPORT_OUTPUT)
            support = "output";
        if (support)
        {
            if (name)
                msg_Dbg(o, "processor format %s is supported for %s", name, support);
            else
                msg_Dbg(o, "processor format (%d) is supported for %s", format, support);
        }
    }
}

#endif
