//-------------------------------------------------------------------------------------
// DirectXTexD3D11.cpp
//  
// DirectX Texture Library - Direct3D 11 helpers
//
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
//
// http://go.microsoft.com/fwlink/?LinkId=248926
//-------------------------------------------------------------------------------------

#include "DirectXTexP.h"

#ifdef _GAMING_XBOX
#error This module is not supported for GDK
#elif !defined(_XBOX_ONE) || !defined(_TITLE)
#include <d3d10.h>
#endif

#ifndef IID_GRAPHICS_PPV_ARGS
#define IID_GRAPHICS_PPV_ARGS(x) IID_PPV_ARGS(x)
#endif

using namespace DirectX;
using Microsoft::WRL::ComPtr;

static_assert(static_cast<int>(TEX_DIMENSION_TEXTURE1D) == static_cast<int>(D3D11_RESOURCE_DIMENSION_TEXTURE1D), "header enum mismatch");
static_assert(static_cast<int>(TEX_DIMENSION_TEXTURE2D) == static_cast<int>(D3D11_RESOURCE_DIMENSION_TEXTURE2D), "header enum mismatch");
static_assert(static_cast<int>(TEX_DIMENSION_TEXTURE3D) == static_cast<int>(D3D11_RESOURCE_DIMENSION_TEXTURE3D), "header enum mismatch");

namespace
{
    HRESULT Capture(
        _In_ ID3D11DeviceContext* pContext,
        _In_ ID3D11Resource* pSource,
        const TexMetadata& metadata,
        const ScratchImage& result) noexcept
    {
        if (!pContext || !pSource || !result.GetPixels())
            return E_POINTER;

#if defined(_XBOX_ONE) && defined(_TITLE)

        ComPtr<ID3D11Device> d3dDevice;
        pContext->GetDevice(d3dDevice.GetAddressOf());

        if (d3dDevice->GetCreationFlags() & D3D11_CREATE_DEVICE_IMMEDIATE_CONTEXT_FAST_SEMANTICS)
        {
            ComPtr<ID3D11DeviceX> d3dDeviceX;
            HRESULT hr = d3dDevice.As(&d3dDeviceX);
            if (FAILED(hr))
                return hr;

            ComPtr<ID3D11DeviceContextX> d3dContextX;
            hr = pContext->QueryInterface(IID_GRAPHICS_PPV_ARGS(d3dContextX.GetAddressOf()));
            if (FAILED(hr))
                return hr;

            UINT64 copyFence = d3dContextX->InsertFence(0);

            while (d3dDeviceX->IsFencePending(copyFence))
            {
                SwitchToThread();
            }
        }

#endif

        if (metadata.IsVolumemap())
        {
            //--- Volume texture ----------------------------------------------------------
            assert(metadata.arraySize == 1);

            size_t height = metadata.height;
            size_t depth = metadata.depth;

            for (size_t level = 0; level < metadata.mipLevels; ++level)
            {
                UINT dindex = D3D11CalcSubresource(static_cast<UINT>(level), 0, static_cast<UINT>(metadata.mipLevels));

                D3D11_MAPPED_SUBRESOURCE mapped;
                HRESULT hr = pContext->Map(pSource, dindex, D3D11_MAP_READ, 0, &mapped);
                if (FAILED(hr))
                    return hr;

                auto pslice = static_cast<const uint8_t*>(mapped.pData);
                if (!pslice)
                {
                    pContext->Unmap(pSource, dindex);
                    return E_POINTER;
                }

                size_t lines = ComputeScanlines(metadata.format, height);
                if (!lines)
                {
                    pContext->Unmap(pSource, dindex);
                    return E_UNEXPECTED;
                }

                for (size_t slice = 0; slice < depth; ++slice)
                {
                    const Image* img = result.GetImage(level, 0, slice);
                    if (!img)
                    {
                        pContext->Unmap(pSource, dindex);
                        return E_FAIL;
                    }

                    if (!img->pixels)
                    {
                        pContext->Unmap(pSource, dindex);
                        return E_POINTER;
                    }

                    const uint8_t* sptr = pslice;
                    uint8_t* dptr = img->pixels;
                    for (size_t h = 0; h < lines; ++h)
                    {
                        size_t msize = std::min<size_t>(img->rowPitch, mapped.RowPitch);
                        memcpy_s(dptr, img->rowPitch, sptr, msize);
                        sptr += mapped.RowPitch;
                        dptr += img->rowPitch;
                    }

                    pslice += mapped.DepthPitch;
                }

                pContext->Unmap(pSource, dindex);

                if (height > 1)
                    height >>= 1;
                if (depth > 1)
                    depth >>= 1;
            }
        }
        else
        {
            //--- 1D or 2D texture --------------------------------------------------------
            assert(metadata.depth == 1);

            for (size_t item = 0; item < metadata.arraySize; ++item)
            {
                size_t height = metadata.height;

                for (size_t level = 0; level < metadata.mipLevels; ++level)
                {
                    UINT dindex = D3D11CalcSubresource(static_cast<UINT>(level), static_cast<UINT>(item), static_cast<UINT>(metadata.mipLevels));

                    D3D11_MAPPED_SUBRESOURCE mapped;
                    HRESULT hr = pContext->Map(pSource, dindex, D3D11_MAP_READ, 0, &mapped);
                    if (FAILED(hr))
                        return hr;

                    const Image* img = result.GetImage(level, item, 0);
                    if (!img)
                    {
                        pContext->Unmap(pSource, dindex);
                        return E_FAIL;
                    }

                    if (!img->pixels)
                    {
                        pContext->Unmap(pSource, dindex);
                        return E_POINTER;
                    }

                    size_t lines = ComputeScanlines(metadata.format, height);
                    if (!lines)
                    {
                        pContext->Unmap(pSource, dindex);
                        return E_UNEXPECTED;
                    }

                    auto sptr = static_cast<const uint8_t*>(mapped.pData);
                    uint8_t* dptr = img->pixels;
                    for (size_t h = 0; h < lines; ++h)
                    {
                        size_t msize = std::min<size_t>(img->rowPitch, mapped.RowPitch);
                        memcpy_s(dptr, img->rowPitch, sptr, msize);
                        sptr += mapped.RowPitch;
                        dptr += img->rowPitch;
                    }

                    pContext->Unmap(pSource, dindex);

                    if (height > 1)
                        height >>= 1;
                }
            }
        }

        return S_OK;
    }
}


//=====================================================================================
// Entry-points
//=====================================================================================

//-------------------------------------------------------------------------------------
// Determine if given texture metadata is supported on the given device
//-------------------------------------------------------------------------------------
_Use_decl_annotations_
bool DirectX::IsSupportedTexture(
    ID3D11Device* pDevice,
    const TexMetadata& metadata) noexcept
{
    if (!pDevice)
        return false;

    D3D_FEATURE_LEVEL fl = pDevice->GetFeatureLevel();

    // Validate format
    DXGI_FORMAT fmt = metadata.format;

    if (!IsValid(fmt))
        return false;

    switch (fmt)
    {
    case DXGI_FORMAT_BC4_TYPELESS:
    case DXGI_FORMAT_BC4_UNORM:
    case DXGI_FORMAT_BC4_SNORM:
    case DXGI_FORMAT_BC5_TYPELESS:
    case DXGI_FORMAT_BC5_UNORM:
    case DXGI_FORMAT_BC5_SNORM:
        if (fl < D3D_FEATURE_LEVEL_10_0)
            return false;
        break;

    case DXGI_FORMAT_BC6H_TYPELESS:
    case DXGI_FORMAT_BC6H_UF16:
    case DXGI_FORMAT_BC6H_SF16:
    case DXGI_FORMAT_BC7_TYPELESS:
    case DXGI_FORMAT_BC7_UNORM:
    case DXGI_FORMAT_BC7_UNORM_SRGB:
        if (fl < D3D_FEATURE_LEVEL_11_0)
            return false;
        break;

    default:
        break;
    }

    // Validate miplevel count
    if (metadata.mipLevels > D3D11_REQ_MIP_LEVELS)
        return false;

    // Validate array size, dimension, and width/height
    size_t arraySize = metadata.arraySize;
    size_t iWidth = metadata.width;
    size_t iHeight = metadata.height;
    size_t iDepth = metadata.depth;

    // Most cases are known apriori based on feature level, but we use this for robustness to handle the few optional cases
    UINT formatSupport = 0;
    HRESULT hr = pDevice->CheckFormatSupport(fmt, &formatSupport);
    if (FAILED(hr))
    {
        formatSupport = 0;
    }

    if (metadata.mipLevels > 1 && !(formatSupport & D3D11_FORMAT_SUPPORT_MIP))
    {
        return false;
    }

    switch (metadata.dimension)
    {
    case TEX_DIMENSION_TEXTURE1D:
        if (!(formatSupport & D3D11_FORMAT_SUPPORT_TEXTURE1D))
            return false;

        if ((arraySize > D3D11_REQ_TEXTURE1D_ARRAY_AXIS_DIMENSION)
            || (iWidth > D3D11_REQ_TEXTURE1D_U_DIMENSION))
            return false;

        if (fl < D3D_FEATURE_LEVEL_11_0)
        {
            if ((arraySize > D3D10_REQ_TEXTURE1D_ARRAY_AXIS_DIMENSION)
                || (iWidth > D3D10_REQ_TEXTURE1D_U_DIMENSION))
                return false;

            if (fl < D3D_FEATURE_LEVEL_10_0)
            {
                if ((arraySize > 1) || (iWidth > D3D_FL9_3_REQ_TEXTURE1D_U_DIMENSION))
                    return false;

                if ((fl < D3D_FEATURE_LEVEL_9_3) && (iWidth > D3D_FL9_1_REQ_TEXTURE1D_U_DIMENSION))
                    return false;
            }
        }
        break;

    case TEX_DIMENSION_TEXTURE2D:
        if (metadata.IsCubemap())
        {
            if (!(formatSupport & D3D11_FORMAT_SUPPORT_TEXTURECUBE))
                return false;

            if ((arraySize > D3D11_REQ_TEXTURE2D_ARRAY_AXIS_DIMENSION)
                || (iWidth > D3D11_REQ_TEXTURECUBE_DIMENSION)
                || (iHeight > D3D11_REQ_TEXTURECUBE_DIMENSION))
                return false;

            if (fl < D3D_FEATURE_LEVEL_11_0)
            {
                if ((arraySize > D3D10_REQ_TEXTURE2D_ARRAY_AXIS_DIMENSION)
                    || (iWidth > D3D10_REQ_TEXTURECUBE_DIMENSION)
                    || (iHeight > D3D10_REQ_TEXTURECUBE_DIMENSION))
                    return false;

                if ((fl < D3D_FEATURE_LEVEL_10_1) && (arraySize != 6))
                    return false;

                if (fl < D3D_FEATURE_LEVEL_10_0)
                {
                    if ((iWidth > D3D_FL9_3_REQ_TEXTURECUBE_DIMENSION)
                        || (iHeight > D3D_FL9_3_REQ_TEXTURECUBE_DIMENSION))
                        return false;

                    if ((fl < D3D_FEATURE_LEVEL_9_3)
                        && ((iWidth > D3D_FL9_1_REQ_TEXTURECUBE_DIMENSION)
                            || (iHeight > D3D_FL9_1_REQ_TEXTURECUBE_DIMENSION)))
                        return false;
                }
            }
        }
        else // Not a cube map
        {
            if (!(formatSupport & D3D11_FORMAT_SUPPORT_TEXTURE2D))
                return false;

            if ((arraySize > D3D11_REQ_TEXTURE2D_ARRAY_AXIS_DIMENSION)
                || (iWidth > D3D11_REQ_TEXTURE2D_U_OR_V_DIMENSION)
                || (iHeight > D3D11_REQ_TEXTURE2D_U_OR_V_DIMENSION))
                return false;

            if (fl < D3D_FEATURE_LEVEL_11_0)
            {
                if ((arraySize > D3D10_REQ_TEXTURE2D_ARRAY_AXIS_DIMENSION)
                    || (iWidth > D3D10_REQ_TEXTURE2D_U_OR_V_DIMENSION)
                    || (iHeight > D3D10_REQ_TEXTURE2D_U_OR_V_DIMENSION))
                    return false;

                if (fl < D3D_FEATURE_LEVEL_10_0)
                {
                    if ((arraySize > 1)
                        || (iWidth > D3D_FL9_3_REQ_TEXTURE2D_U_OR_V_DIMENSION)
                        || (iHeight > D3D_FL9_3_REQ_TEXTURE2D_U_OR_V_DIMENSION))
                        return false;

                    if ((fl < D3D_FEATURE_LEVEL_9_3)
                        && ((iWidth > D3D_FL9_1_REQ_TEXTURE2D_U_OR_V_DIMENSION)
                            || (iHeight > D3D_FL9_1_REQ_TEXTURE2D_U_OR_V_DIMENSION)))
                        return false;
                }
            }
        }
        break;

    case TEX_DIMENSION_TEXTURE3D:
        if (!(formatSupport & D3D11_FORMAT_SUPPORT_TEXTURE3D))
            return false;

        if ((arraySize > 1)
            || (iWidth > D3D11_REQ_TEXTURE3D_U_V_OR_W_DIMENSION)
            || (iHeight > D3D11_REQ_TEXTURE3D_U_V_OR_W_DIMENSION)
            || (iDepth > D3D11_REQ_TEXTURE3D_U_V_OR_W_DIMENSION))
            return false;

        if (fl < D3D_FEATURE_LEVEL_11_0)
        {
            if ((iWidth > D3D10_REQ_TEXTURE3D_U_V_OR_W_DIMENSION)
                || (iHeight > D3D10_REQ_TEXTURE3D_U_V_OR_W_DIMENSION)
                || (iDepth > D3D10_REQ_TEXTURE3D_U_V_OR_W_DIMENSION))
                return false;

            if (fl < D3D_FEATURE_LEVEL_10_0)
            {
                if ((iWidth > D3D_FL9_1_REQ_TEXTURE3D_U_V_OR_W_DIMENSION)
                    || (iHeight > D3D_FL9_1_REQ_TEXTURE3D_U_V_OR_W_DIMENSION)
                    || (iDepth > D3D_FL9_1_REQ_TEXTURE3D_U_V_OR_W_DIMENSION))
                    return false;
            }
        }
        break;

    default:
        // Not a supported dimension
        return false;
    }

    return true;
}


//-------------------------------------------------------------------------------------
// Create a texture resource
//-------------------------------------------------------------------------------------
_Use_decl_annotations_
HRESULT DirectX::CreateTexture(
    ID3D11Device* pDevice,
    const Image* srcImages,
    size_t nimages,
    const TexMetadata& metadata,
    ID3D11Resource** ppResource) noexcept
{
    return CreateTextureEx(
        pDevice, srcImages, nimages, metadata,
        D3D11_USAGE_DEFAULT, D3D11_BIND_SHADER_RESOURCE, 0, 0, false,
        ppResource);
}

_Use_decl_annotations_
HRESULT DirectX::CreateTextureEx(
    ID3D11Device* pDevice,
    const Image* srcImages,
    size_t nimages,
    const TexMetadata& metadata,
    D3D11_USAGE usage,
    unsigned int bindFlags,
    unsigned int cpuAccessFlags,
    unsigned int miscFlags,
    bool forceSRGB,
    ID3D11Resource** ppResource) noexcept
{
    if (!pDevice || !srcImages || !nimages || !ppResource)
        return E_INVALIDARG;

    *ppResource = nullptr;

    if (!metadata.mipLevels || !metadata.arraySize)
        return E_INVALIDARG;

    if ((metadata.width > UINT32_MAX) || (metadata.height > UINT32_MAX)
        || (metadata.mipLevels > UINT16_MAX) || (metadata.arraySize > UINT16_MAX))
        return E_INVALIDARG;

    std::unique_ptr<D3D11_SUBRESOURCE_DATA[]> initData(new (std::nothrow) D3D11_SUBRESOURCE_DATA[metadata.mipLevels * metadata.arraySize]);
    if (!initData)
        return E_OUTOFMEMORY;

    // Fill out subresource array
    if (metadata.IsVolumemap())
    {
        //--- Volume case -------------------------------------------------------------
        if (!metadata.depth)
            return E_INVALIDARG;

        if (metadata.depth > UINT16_MAX)
            return E_INVALIDARG;

        if (metadata.arraySize > 1)
            // Direct3D 11 doesn't support arrays of 3D textures
            return HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED);

        size_t depth = metadata.depth;

        size_t idx = 0;
        for (size_t level = 0; level < metadata.mipLevels; ++level)
        {
            size_t index = metadata.ComputeIndex(level, 0, 0);
            if (index >= nimages)
                return E_FAIL;

            const Image& img = srcImages[index];

            if (img.format != metadata.format)
                return E_FAIL;

            if (!img.pixels)
                return E_POINTER;

            // Verify pixels in image 1 .. (depth-1) are exactly image->slicePitch apart
            // For 3D textures, this relies on all slices of the same miplevel being continous in memory
            // (this is how ScratchImage lays them out), which is why we just give the 0th slice to Direct3D 11
            const uint8_t* pSlice = img.pixels + img.slicePitch;
            for (size_t slice = 1; slice < depth; ++slice)
            {
                size_t tindex = metadata.ComputeIndex(level, 0, slice);
                if (tindex >= nimages)
                    return E_FAIL;

                const Image& timg = srcImages[tindex];

                if (!timg.pixels)
                    return E_POINTER;

                if (timg.pixels != pSlice
                    || timg.format != metadata.format
                    || timg.rowPitch != img.rowPitch
                    || timg.slicePitch != img.slicePitch)
                    return E_FAIL;

                pSlice = timg.pixels + img.slicePitch;
            }

            assert(idx < (metadata.mipLevels * metadata.arraySize));

            initData[idx].pSysMem = img.pixels;
            initData[idx].SysMemPitch = static_cast<DWORD>(img.rowPitch);
            initData[idx].SysMemSlicePitch = static_cast<DWORD>(img.slicePitch);
            ++idx;

            if (depth > 1)
                depth >>= 1;
        }
    }
    else
    {
        //--- 1D or 2D texture case ---------------------------------------------------
        size_t idx = 0;
        for (size_t item = 0; item < metadata.arraySize; ++item)
        {
            for (size_t level = 0; level < metadata.mipLevels; ++level)
            {
                size_t index = metadata.ComputeIndex(level, item, 0);
                if (index >= nimages)
                    return E_FAIL;

                const Image& img = srcImages[index];

                if (img.format != metadata.format)
                    return E_FAIL;

                if (!img.pixels)
                    return E_POINTER;

                assert(idx < (metadata.mipLevels * metadata.arraySize));

                initData[idx].pSysMem = img.pixels;
                initData[idx].SysMemPitch = static_cast<DWORD>(img.rowPitch);
                initData[idx].SysMemSlicePitch = static_cast<DWORD>(img.slicePitch);
                ++idx;
            }
        }
    }

    // Create texture using static initialization data
    HRESULT hr = E_UNEXPECTED;

    DXGI_FORMAT tformat = (forceSRGB) ? MakeSRGB(metadata.format) : metadata.format;

    switch (metadata.dimension)
    {
    case TEX_DIMENSION_TEXTURE1D:
    {
        D3D11_TEXTURE1D_DESC desc = {};
        desc.Width = static_cast<UINT>(metadata.width);
        desc.MipLevels = static_cast<UINT>(metadata.mipLevels);
        desc.ArraySize = static_cast<UINT>(metadata.arraySize);
        desc.Format = tformat;
        desc.Usage = usage;
        desc.BindFlags = bindFlags;
        desc.CPUAccessFlags = cpuAccessFlags;
        desc.MiscFlags = miscFlags & ~static_cast<uint32_t>(D3D11_RESOURCE_MISC_TEXTURECUBE);

        hr = pDevice->CreateTexture1D(&desc, initData.get(), reinterpret_cast<ID3D11Texture1D**>(ppResource));
    }
    break;

    case TEX_DIMENSION_TEXTURE2D:
    {
        D3D11_TEXTURE2D_DESC desc = {};
        desc.Width = static_cast<UINT>(metadata.width);
        desc.Height = static_cast<UINT>(metadata.height);
        desc.MipLevels = static_cast<UINT>(metadata.mipLevels);
        desc.ArraySize = static_cast<UINT>(metadata.arraySize);
        desc.Format = tformat;
        desc.SampleDesc.Count = 1;
        desc.SampleDesc.Quality = 0;
        desc.Usage = usage;
        desc.BindFlags = bindFlags;
        desc.CPUAccessFlags = cpuAccessFlags;
        if (metadata.IsCubemap())
            desc.MiscFlags = miscFlags | D3D11_RESOURCE_MISC_TEXTURECUBE;
        else
            desc.MiscFlags = miscFlags & ~static_cast<uint32_t>(D3D11_RESOURCE_MISC_TEXTURECUBE);

        hr = pDevice->CreateTexture2D(&desc, initData.get(), reinterpret_cast<ID3D11Texture2D**>(ppResource));
    }
    break;

    case TEX_DIMENSION_TEXTURE3D:
    {
        D3D11_TEXTURE3D_DESC desc = {};
        desc.Width = static_cast<UINT>(metadata.width);
        desc.Height = static_cast<UINT>(metadata.height);
        desc.Depth = static_cast<UINT>(metadata.depth);
        desc.MipLevels = static_cast<UINT>(metadata.mipLevels);
        desc.Format = tformat;
        desc.Usage = usage;
        desc.BindFlags = bindFlags;
        desc.CPUAccessFlags = cpuAccessFlags;
        desc.MiscFlags = miscFlags & ~static_cast<uint32_t>(D3D11_RESOURCE_MISC_TEXTURECUBE);

        hr = pDevice->CreateTexture3D(&desc, initData.get(), reinterpret_cast<ID3D11Texture3D**>(ppResource));
    }
    break;
    }

    return hr;
}


//-------------------------------------------------------------------------------------
// Create a shader resource view and associated texture
//-------------------------------------------------------------------------------------
_Use_decl_annotations_
HRESULT DirectX::CreateShaderResourceView(
    ID3D11Device* pDevice,
    const Image* srcImages,
    size_t nimages,
    const TexMetadata& metadata,
    ID3D11ShaderResourceView** ppSRV) noexcept
{
    return CreateShaderResourceViewEx(
        pDevice, srcImages, nimages, metadata,
        D3D11_USAGE_DEFAULT, D3D11_BIND_SHADER_RESOURCE, 0, 0, false,
        ppSRV);
}

_Use_decl_annotations_
HRESULT DirectX::CreateShaderResourceViewEx(
    ID3D11Device* pDevice,
    const Image* srcImages,
    size_t nimages,
    const TexMetadata& metadata,
    D3D11_USAGE usage,
    unsigned int bindFlags,
    unsigned int cpuAccessFlags,
    unsigned int miscFlags,
    bool forceSRGB,
    ID3D11ShaderResourceView** ppSRV) noexcept
{
    if (!ppSRV)
        return E_INVALIDARG;

    *ppSRV = nullptr;

    if (!(bindFlags & D3D11_BIND_SHADER_RESOURCE))
        return E_INVALIDARG;

    ComPtr<ID3D11Resource> resource;
    HRESULT hr = CreateTextureEx(pDevice, srcImages, nimages, metadata,
        usage, bindFlags, cpuAccessFlags, miscFlags, forceSRGB,
        resource.GetAddressOf());
    if (FAILED(hr))
        return hr;

    assert(resource);

    D3D11_SHADER_RESOURCE_VIEW_DESC SRVDesc = {};
    if (forceSRGB)
        SRVDesc.Format = MakeSRGB(metadata.format);
    else
        SRVDesc.Format = metadata.format;

    switch (metadata.dimension)
    {
    case TEX_DIMENSION_TEXTURE1D:
        if (metadata.arraySize > 1)
        {
            SRVDesc.ViewDimension = D3D_SRV_DIMENSION_TEXTURE1DARRAY;
            SRVDesc.Texture1DArray.MipLevels = static_cast<UINT>(metadata.mipLevels);
            SRVDesc.Texture1DArray.ArraySize = static_cast<UINT>(metadata.arraySize);
        }
        else
        {
            SRVDesc.ViewDimension = D3D_SRV_DIMENSION_TEXTURE1D;
            SRVDesc.Texture1D.MipLevels = static_cast<UINT>(metadata.mipLevels);
        }
        break;

    case TEX_DIMENSION_TEXTURE2D:
        if (metadata.IsCubemap())
        {
            if (metadata.arraySize > 6)
            {
                assert((metadata.arraySize % 6) == 0);
                SRVDesc.ViewDimension = D3D_SRV_DIMENSION_TEXTURECUBEARRAY;
                SRVDesc.TextureCubeArray.MipLevels = static_cast<UINT>(metadata.mipLevels);
                SRVDesc.TextureCubeArray.NumCubes = static_cast<UINT>(metadata.arraySize / 6);
            }
            else
            {
                SRVDesc.ViewDimension = D3D_SRV_DIMENSION_TEXTURECUBE;
                SRVDesc.TextureCube.MipLevels = static_cast<UINT>(metadata.mipLevels);
            }
        }
        else if (metadata.arraySize > 1)
        {
            SRVDesc.ViewDimension = D3D_SRV_DIMENSION_TEXTURE2DARRAY;
            SRVDesc.Texture2DArray.MipLevels = static_cast<UINT>(metadata.mipLevels);
            SRVDesc.Texture2DArray.ArraySize = static_cast<UINT>(metadata.arraySize);
        }
        else
        {
            SRVDesc.ViewDimension = D3D_SRV_DIMENSION_TEXTURE2D;
            SRVDesc.Texture2D.MipLevels = static_cast<UINT>(metadata.mipLevels);
        }
        break;

    case TEX_DIMENSION_TEXTURE3D:
        assert(metadata.arraySize == 1);
        SRVDesc.ViewDimension = D3D_SRV_DIMENSION_TEXTURE3D;
        SRVDesc.Texture3D.MipLevels = static_cast<UINT>(metadata.mipLevels);
        break;

    default:
        return E_FAIL;
    }

    hr = pDevice->CreateShaderResourceView(resource.Get(), &SRVDesc, ppSRV);
    if (FAILED(hr))
        return hr;

    assert(*ppSRV);

    return S_OK;
}


//-------------------------------------------------------------------------------------
// Save a texture resource
//-------------------------------------------------------------------------------------
_Use_decl_annotations_
HRESULT DirectX::CaptureTexture(
    ID3D11Device* pDevice,
    ID3D11DeviceContext* pContext,
    ID3D11Resource* pSource,
    ScratchImage& result) noexcept
{
    if (!pDevice || !pContext || !pSource)
        return E_INVALIDARG;

    D3D11_RESOURCE_DIMENSION resType = D3D11_RESOURCE_DIMENSION_UNKNOWN;
    pSource->GetType(&resType);

    HRESULT hr = E_UNEXPECTED;

    switch (resType)
    {
    case D3D11_RESOURCE_DIMENSION_TEXTURE1D:
    {
        ComPtr<ID3D11Texture1D> pTexture;
        hr = pSource->QueryInterface(IID_GRAPHICS_PPV_ARGS(pTexture.GetAddressOf()));
        if (FAILED(hr))
            break;

        assert(pTexture);

        D3D11_TEXTURE1D_DESC desc;
        pTexture->GetDesc(&desc);

        ComPtr<ID3D11Texture1D> pStaging;
        if ((desc.Usage == D3D11_USAGE_STAGING) && (desc.CPUAccessFlags & D3D11_CPU_ACCESS_READ))
        {
            // Handle case where the source is already a staging texture we can use directly
            pStaging = pTexture;
        }
        else
        {
            desc.BindFlags = 0;
            desc.MiscFlags = 0;
            desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
            desc.Usage = D3D11_USAGE_STAGING;

            hr = pDevice->CreateTexture1D(&desc, nullptr, pStaging.GetAddressOf());
            if (FAILED(hr))
                break;

            assert(pStaging);

            pContext->CopyResource(pStaging.Get(), pSource);
        }

        TexMetadata mdata;
        mdata.width = desc.Width;
        mdata.height = mdata.depth = 1;
        mdata.arraySize = desc.ArraySize;
        mdata.mipLevels = desc.MipLevels;
        mdata.miscFlags = 0;
        mdata.miscFlags2 = 0;
        mdata.format = desc.Format;
        mdata.dimension = TEX_DIMENSION_TEXTURE1D;

        hr = result.Initialize(mdata);
        if (FAILED(hr))
            break;

        hr = Capture(pContext, pStaging.Get(), mdata, result);
    }
    break;

    case D3D11_RESOURCE_DIMENSION_TEXTURE2D:
    {
        ComPtr<ID3D11Texture2D> pTexture;
        hr = pSource->QueryInterface(IID_GRAPHICS_PPV_ARGS(pTexture.GetAddressOf()));
        if (FAILED(hr))
            break;

        assert(pTexture);

        D3D11_TEXTURE2D_DESC desc;
        pTexture->GetDesc(&desc);

        ComPtr<ID3D11Texture2D> pStaging;
        if (desc.SampleDesc.Count > 1)
        {
            desc.SampleDesc.Count = 1;
            desc.SampleDesc.Quality = 0;

            ComPtr<ID3D11Texture2D> pTemp;
            hr = pDevice->CreateTexture2D(&desc, nullptr, pTemp.GetAddressOf());
            if (FAILED(hr))
                break;

            assert(pTemp);

            DXGI_FORMAT fmt = desc.Format;
            if (IsTypeless(fmt))
            {
                // Assume a UNORM if it exists otherwise use FLOAT
                fmt = MakeTypelessUNORM(fmt);
                fmt = MakeTypelessFLOAT(fmt);
            }

            UINT support = 0;
            hr = pDevice->CheckFormatSupport(fmt, &support);
            if (FAILED(hr))
                break;

            if (!(support & D3D11_FORMAT_SUPPORT_MULTISAMPLE_RESOLVE))
            {
                hr = E_FAIL;
                break;
            }

            for (UINT item = 0; item < desc.ArraySize; ++item)
            {
                for (UINT level = 0; level < desc.MipLevels; ++level)
                {
                    UINT index = D3D11CalcSubresource(level, item, desc.MipLevels);
                    pContext->ResolveSubresource(pTemp.Get(), index, pSource, index, fmt);
                }
            }

            desc.BindFlags = 0;
            desc.MiscFlags &= D3D11_RESOURCE_MISC_TEXTURECUBE;
            desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
            desc.Usage = D3D11_USAGE_STAGING;

            hr = pDevice->CreateTexture2D(&desc, nullptr, pStaging.GetAddressOf());
            if (FAILED(hr))
                break;

            assert(pStaging);

            pContext->CopyResource(pStaging.Get(), pTemp.Get());
        }
        else if ((desc.Usage == D3D11_USAGE_STAGING) && (desc.CPUAccessFlags & D3D11_CPU_ACCESS_READ))
        {
            // Handle case where the source is already a staging texture we can use directly
            pStaging = pTexture;
        }
        else
        {
            desc.BindFlags = 0;
            desc.MiscFlags &= D3D11_RESOURCE_MISC_TEXTURECUBE;
            desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
            desc.Usage = D3D11_USAGE_STAGING;

            hr = pDevice->CreateTexture2D(&desc, nullptr, &pStaging);
            if (FAILED(hr))
                break;

            assert(pStaging);

            pContext->CopyResource(pStaging.Get(), pSource);
        }

        TexMetadata mdata;
        mdata.width = desc.Width;
        mdata.height = desc.Height;
        mdata.depth = 1;
        mdata.arraySize = desc.ArraySize;
        mdata.mipLevels = desc.MipLevels;
        mdata.miscFlags = (desc.MiscFlags & D3D11_RESOURCE_MISC_TEXTURECUBE) ? TEX_MISC_TEXTURECUBE : 0u;
        mdata.miscFlags2 = 0;
        mdata.format = desc.Format;
        mdata.dimension = TEX_DIMENSION_TEXTURE2D;

        hr = result.Initialize(mdata);
        if (FAILED(hr))
            break;

        hr = Capture(pContext, pStaging.Get(), mdata, result);
    }
    break;

    case D3D11_RESOURCE_DIMENSION_TEXTURE3D:
    {
        ComPtr<ID3D11Texture3D> pTexture;
        hr = pSource->QueryInterface(IID_GRAPHICS_PPV_ARGS(pTexture.GetAddressOf()));
        if (FAILED(hr))
            break;

        assert(pTexture);

        D3D11_TEXTURE3D_DESC desc;
        pTexture->GetDesc(&desc);

        ComPtr<ID3D11Texture3D> pStaging;
        if ((desc.Usage == D3D11_USAGE_STAGING) && (desc.CPUAccessFlags & D3D11_CPU_ACCESS_READ))
        {
            // Handle case where the source is already a staging texture we can use directly
            pStaging = pTexture;
        }
        else
        {
            desc.BindFlags = 0;
            desc.MiscFlags = 0;
            desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
            desc.Usage = D3D11_USAGE_STAGING;

            hr = pDevice->CreateTexture3D(&desc, nullptr, pStaging.GetAddressOf());
            if (FAILED(hr))
                break;

            assert(pStaging);

            pContext->CopyResource(pStaging.Get(), pSource);
        }

        TexMetadata mdata;
        mdata.width = desc.Width;
        mdata.height = desc.Height;
        mdata.depth = desc.Depth;
        mdata.arraySize = 1;
        mdata.mipLevels = desc.MipLevels;
        mdata.miscFlags = 0;
        mdata.miscFlags2 = 0;
        mdata.format = desc.Format;
        mdata.dimension = TEX_DIMENSION_TEXTURE3D;

        hr = result.Initialize(mdata);
        if (FAILED(hr))
            break;

        hr = Capture(pContext, pStaging.Get(), mdata, result);
    }
    break;

    default:
        hr = E_FAIL;
        break;
    }

    if (FAILED(hr))
    {
        result.Release();
        return hr;
    }

    return S_OK;
}
