/*
 *  Copyright 2019-2020 Diligent Graphics LLC
 *  Copyright 2015-2019 Egor Yusov
 *  
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *  
 *      http://www.apache.org/licenses/LICENSE-2.0
 *  
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 *  In no event and under no legal theory, whether in tort (including negligence), 
 *  contract, or otherwise, unless required by applicable law (such as deliberate 
 *  and grossly negligent acts) or agreed to in writing, shall any Contributor be
 *  liable for any damages, including any direct, indirect, special, incidental, 
 *  or consequential damages of any character arising as a result of this License or 
 *  out of the use or inability to use the software (including but not limited to damages 
 *  for loss of goodwill, work stoppage, computer failure or malfunction, or any and 
 *  all other commercial damages or losses), even if such Contributor has been advised 
 *  of the possibility of such damages.
 */

#include "Texture.h"

#include <algorithm>

#include "DeviceContext.h"
#include "GraphicsAccessories.hpp"

namespace Diligent
{

void ValidateTextureDesc(const TextureDesc& Desc) noexcept(false)
{
#define LOG_TEXTURE_ERROR_AND_THROW(...) LOG_ERROR_AND_THROW("Texture '", (Desc.Name ? Desc.Name : ""), "': ", ##__VA_ARGS__)

    const auto& FmtAttribs = GetTextureFormatAttribs(Desc.Format);

    if (Desc.Type == RESOURCE_DIM_UNDEFINED)
    {
        LOG_TEXTURE_ERROR_AND_THROW("Resource dimension is undefined.");
    }

    if (!(Desc.Type >= RESOURCE_DIM_TEX_1D && Desc.Type <= RESOURCE_DIM_TEX_CUBE_ARRAY))
    {
        LOG_TEXTURE_ERROR_AND_THROW("Unexpected resource dimension.");
    }

    if (Desc.Width == 0)
    {
        LOG_TEXTURE_ERROR_AND_THROW("Texture width cannot be zero.");
    }

    // Perform some parameter correctness check
    if (Desc.Type == RESOURCE_DIM_TEX_1D || Desc.Type == RESOURCE_DIM_TEX_1D_ARRAY)
    {
        if (Desc.Height != FmtAttribs.BlockHeight)
        {
            if (FmtAttribs.BlockHeight == 1)
            {
                LOG_TEXTURE_ERROR_AND_THROW("Height (", Desc.Height, ") of a Texture 1D/Texture 1D Array must be 1.");
            }
            else
            {
                LOG_TEXTURE_ERROR_AND_THROW("For block-compressed formats, the height (", Desc.Height,
                                            ") of a Texture 1D/Texture 1D Array must be equal to the compressed block height (",
                                            Uint32{FmtAttribs.BlockHeight}, ").");
            }
        }
    }
    else
    {
        if (Desc.Height == 0)
            LOG_TEXTURE_ERROR_AND_THROW("Texture height cannot be zero.");
    }

    if (Desc.Type == RESOURCE_DIM_TEX_3D && Desc.Depth == 0)
    {
        LOG_TEXTURE_ERROR_AND_THROW("3D texture depth cannot be zero.");
    }

    if (Desc.Type == RESOURCE_DIM_TEX_1D || Desc.Type == RESOURCE_DIM_TEX_2D)
    {
        if (Desc.ArraySize != 1)
            LOG_TEXTURE_ERROR_AND_THROW("Texture 1D/2D must have one array slice (", Desc.ArraySize, " provided). Use Texture 1D/2D array if you need more than one slice.");
    }

    if (Desc.Type == RESOURCE_DIM_TEX_CUBE || Desc.Type == RESOURCE_DIM_TEX_CUBE_ARRAY)
    {
        if (Desc.Width != Desc.Height)
            LOG_TEXTURE_ERROR_AND_THROW("For cube map textures, texture width (", Desc.Width, " provided) must match texture height (", Desc.Height, " provided).");

        if (Desc.ArraySize < 6)
            LOG_TEXTURE_ERROR_AND_THROW("Texture cube/cube array must have at least 6 slices (", Desc.ArraySize, " provided).");
    }

    Uint32 MaxDim = 0;
    if (Desc.Type == RESOURCE_DIM_TEX_1D || Desc.Type == RESOURCE_DIM_TEX_1D_ARRAY)
        MaxDim = Desc.Width;
    else if (Desc.Type == RESOURCE_DIM_TEX_2D || Desc.Type == RESOURCE_DIM_TEX_2D_ARRAY || Desc.Type == RESOURCE_DIM_TEX_CUBE || Desc.Type == RESOURCE_DIM_TEX_CUBE_ARRAY)
        MaxDim = std::max(Desc.Width, Desc.Height);
    else if (Desc.Type == RESOURCE_DIM_TEX_3D)
        MaxDim = std::max(std::max(Desc.Width, Desc.Height), Desc.Depth);
    VERIFY(MaxDim >= (1U << (Desc.MipLevels - 1)), "Texture '", Desc.Name ? Desc.Name : "", "': Incorrect number of Mip levels (", Desc.MipLevels, ").");

    if (Desc.SampleCount > 1)
    {
        if (!(Desc.Type == RESOURCE_DIM_TEX_2D || Desc.Type == RESOURCE_DIM_TEX_2D_ARRAY))
            LOG_TEXTURE_ERROR_AND_THROW("Only Texture 2D/Texture 2D Array can be multisampled");

        if (Desc.MipLevels != 1)
            LOG_TEXTURE_ERROR_AND_THROW("Multisampled textures must have one mip level (", Desc.MipLevels, " levels specified).");

        if (Desc.BindFlags & BIND_UNORDERED_ACCESS)
            LOG_TEXTURE_ERROR_AND_THROW("UAVs are not allowed for multisampled resources");
    }

    if ((Desc.BindFlags & BIND_RENDER_TARGET) &&
        (Desc.Format == TEX_FORMAT_R8_SNORM || Desc.Format == TEX_FORMAT_RG8_SNORM || Desc.Format == TEX_FORMAT_RGBA8_SNORM ||
         Desc.Format == TEX_FORMAT_R16_SNORM || Desc.Format == TEX_FORMAT_RG16_SNORM || Desc.Format == TEX_FORMAT_RGBA16_SNORM))
    {
        const auto* FmtName = GetTextureFormatAttribs(Desc.Format).Name;
        LOG_WARNING_MESSAGE(FmtName, " texture is created with BIND_RENDER_TARGET flag set.\n"
                                     "There might be an issue in OpenGL driver on NVidia hardware: when rendering to SNORM textures, all negative values are clamped to zero.\n"
                                     "Use UNORM format instead.");
    }

    if (Desc.Usage == USAGE_STAGING)
    {
        if (Desc.BindFlags != 0)
            LOG_TEXTURE_ERROR_AND_THROW("Staging textures cannot be bound to any GPU pipeline stage.");

        if (Desc.MiscFlags & MISC_TEXTURE_FLAG_GENERATE_MIPS)
            LOG_TEXTURE_ERROR_AND_THROW("Mipmaps cannot be autogenerated for staging textures.");

        if (Desc.CPUAccessFlags == 0)
            LOG_TEXTURE_ERROR_AND_THROW("Staging textures must specify CPU access flags.");

        if ((Desc.CPUAccessFlags & (CPU_ACCESS_READ | CPU_ACCESS_WRITE)) == (CPU_ACCESS_READ | CPU_ACCESS_WRITE))
            LOG_TEXTURE_ERROR_AND_THROW("Staging textures must use exactly one of ACESS_READ or ACCESS_WRITE flags.");
    }
    else if (Desc.Usage == USAGE_UNIFIED)
    {
        LOG_TEXTURE_ERROR_AND_THROW("USAGE_UNIFIED textures are currently not supported.");
    }
}


void ValidateTextureRegion(const TextureDesc& TexDesc, Uint32 MipLevel, Uint32 Slice, const Box& Box)
{
#define VERIFY_TEX_PARAMS(Expr, ...)                                                          \
    do                                                                                        \
    {                                                                                         \
        if (!(Expr))                                                                          \
        {                                                                                     \
            LOG_ERROR("Texture '", (TexDesc.Name ? TexDesc.Name : ""), "': ", ##__VA_ARGS__); \
        }                                                                                     \
    } while (false)

#ifdef DILIGENT_DEVELOPMENT
    VERIFY_TEX_PARAMS(MipLevel < TexDesc.MipLevels, "Mip level (", MipLevel, ") is out of allowed range [0, ", TexDesc.MipLevels - 1, "]");
    VERIFY_TEX_PARAMS(Box.MinX < Box.MaxX, "Invalid X range: ", Box.MinX, "..", Box.MaxX);
    VERIFY_TEX_PARAMS(Box.MinY < Box.MaxY, "Invalid Y range: ", Box.MinY, "..", Box.MaxY);
    VERIFY_TEX_PARAMS(Box.MinZ < Box.MaxZ, "Invalid Z range: ", Box.MinZ, "..", Box.MaxZ);

    if (TexDesc.Type == RESOURCE_DIM_TEX_1D_ARRAY ||
        TexDesc.Type == RESOURCE_DIM_TEX_2D_ARRAY ||
        TexDesc.Type == RESOURCE_DIM_TEX_CUBE ||
        TexDesc.Type == RESOURCE_DIM_TEX_CUBE_ARRAY)
    {
        VERIFY_TEX_PARAMS(Slice < TexDesc.ArraySize, "Array slice (", Slice, ") is out of range [0,", TexDesc.ArraySize - 1, "].");
    }
    else
    {
        VERIFY_TEX_PARAMS(Slice == 0, "Array slice (", Slice, ") must be 0 for non-array textures.");
    }

    const auto& FmtAttribs = GetTextureFormatAttribs(TexDesc.Format);

    Uint32 MipWidth = std::max(TexDesc.Width >> MipLevel, 1U);
    if (FmtAttribs.ComponentType == COMPONENT_TYPE_COMPRESSED)
    {
        VERIFY_EXPR((FmtAttribs.BlockWidth & (FmtAttribs.BlockWidth - 1)) == 0);
        Uint32 BlockAlignedMipWidth = (MipWidth + (FmtAttribs.BlockWidth - 1)) & ~(FmtAttribs.BlockWidth - 1);
        VERIFY_TEX_PARAMS(Box.MaxX <= BlockAlignedMipWidth, "Region max X coordinate (", Box.MaxX, ") is out of allowed range [0, ", BlockAlignedMipWidth, "].");
        VERIFY_TEX_PARAMS((Box.MinX % FmtAttribs.BlockWidth) == 0, "For compressed formats, the region min X coordinate (", Box.MinX, ") must be a multiple of block width (", Uint32{FmtAttribs.BlockWidth}, ").");
        VERIFY_TEX_PARAMS((Box.MaxX % FmtAttribs.BlockWidth) == 0 || Box.MaxX == MipWidth, "For compressed formats, the region max X coordinate (", Box.MaxX, ") must be a multiple of block width (", Uint32{FmtAttribs.BlockWidth}, ") or equal the mip level width (", MipWidth, ").");
    }
    else
        VERIFY_TEX_PARAMS(Box.MaxX <= MipWidth, "Region max X coordinate (", Box.MaxX, ") is out of allowed range [0, ", MipWidth, "].");

    if (TexDesc.Type != RESOURCE_DIM_TEX_1D &&
        TexDesc.Type != RESOURCE_DIM_TEX_1D_ARRAY)
    {
        Uint32 MipHeight = std::max(TexDesc.Height >> MipLevel, 1U);
        if (FmtAttribs.ComponentType == COMPONENT_TYPE_COMPRESSED)
        {
            VERIFY_EXPR((FmtAttribs.BlockHeight & (FmtAttribs.BlockHeight - 1)) == 0);
            Uint32 BlockAlignedMipHeight = (MipHeight + (FmtAttribs.BlockHeight - 1)) & ~(FmtAttribs.BlockHeight - 1);
            VERIFY_TEX_PARAMS(Box.MaxY <= BlockAlignedMipHeight, "Region max Y coordinate (", Box.MaxY, ") is out of allowed range [0, ", BlockAlignedMipHeight, "].");
            VERIFY_TEX_PARAMS((Box.MinY % FmtAttribs.BlockHeight) == 0, "For compressed formats, the region min Y coordinate (", Box.MinY, ") must be a multiple of block height (", Uint32{FmtAttribs.BlockHeight}, ").");
            VERIFY_TEX_PARAMS((Box.MaxY % FmtAttribs.BlockHeight) == 0 || Box.MaxY == MipHeight, "For compressed formats, the region max Y coordinate (", Box.MaxY, ") must be a multiple of block height (", Uint32{FmtAttribs.BlockHeight}, ") or equal the mip level height (", MipHeight, ").");
        }
        else
            VERIFY_TEX_PARAMS(Box.MaxY <= MipHeight, "Region max Y coordinate (", Box.MaxY, ") is out of allowed range [0, ", MipHeight, "].");
    }

    if (TexDesc.Type == RESOURCE_DIM_TEX_3D)
    {
        Uint32 MipDepth = std::max(TexDesc.Depth >> MipLevel, 1U);
        VERIFY_TEX_PARAMS(Box.MaxZ <= MipDepth, "Region max Z coordinate (", Box.MaxZ, ") is out of allowed range  [0, ", MipDepth, "].");
    }
    else
    {
        VERIFY_TEX_PARAMS(Box.MinZ == 0, "Region min Z (", Box.MinZ, ") must be 0 for all but 3D textures.");
        VERIFY_TEX_PARAMS(Box.MaxZ == 1, "Region max Z (", Box.MaxZ, ") must be 1 for all but 3D textures.");
    }
#endif
}

void ValidateUpdateTextureParams(const TextureDesc& TexDesc, Uint32 MipLevel, Uint32 Slice, const Box& DstBox, const TextureSubResData& SubresData)
{
    VERIFY((SubresData.pData != nullptr) ^ (SubresData.pSrcBuffer != nullptr), "Either CPU data pointer (pData) or GPU buffer (pSrcBuffer) must not be null, but not both.");
    ValidateTextureRegion(TexDesc, MipLevel, Slice, DstBox);

#ifdef DILIGENT_DEVELOPMENT
    VERIFY_TEX_PARAMS(TexDesc.SampleCount == 1, "Only non-multisampled textures can be updated with UpdateData().");
    VERIFY_TEX_PARAMS((SubresData.Stride & 0x03) == 0, "Texture data stride (", SubresData.Stride, ") must be at least 32-bit aligned.");
    VERIFY_TEX_PARAMS((SubresData.DepthStride & 0x03) == 0, "Texture data depth stride (", SubresData.DepthStride, ") must be at least 32-bit aligned.");

    auto        UpdateRegionWidth  = DstBox.MaxX - DstBox.MinX;
    auto        UpdateRegionHeight = DstBox.MaxY - DstBox.MinY;
    auto        UpdateRegionDepth  = DstBox.MaxZ - DstBox.MinZ;
    const auto& FmtAttribs         = GetTextureFormatAttribs(TexDesc.Format);
    Uint32      RowSize            = 0;
    Uint32      RowCount           = 0;
    if (FmtAttribs.ComponentType == COMPONENT_TYPE_COMPRESSED)
    {
        // Align update region size by the block size. This is only necessary when updating
        // coarse mip levels. Otherwise UpdateRegionWidth/Height should be multiples of block size
        VERIFY_EXPR((FmtAttribs.BlockWidth & (FmtAttribs.BlockWidth - 1)) == 0);
        VERIFY_EXPR((FmtAttribs.BlockHeight & (FmtAttribs.BlockHeight - 1)) == 0);
        UpdateRegionWidth  = (UpdateRegionWidth + (FmtAttribs.BlockWidth - 1)) & ~(FmtAttribs.BlockWidth - 1);
        UpdateRegionHeight = (UpdateRegionHeight + (FmtAttribs.BlockHeight - 1)) & ~(FmtAttribs.BlockHeight - 1);
        RowSize            = UpdateRegionWidth / Uint32{FmtAttribs.BlockWidth} * Uint32{FmtAttribs.ComponentSize};
        RowCount           = UpdateRegionHeight / FmtAttribs.BlockHeight;
    }
    else
    {
        RowSize  = UpdateRegionWidth * Uint32{FmtAttribs.ComponentSize} * Uint32{FmtAttribs.NumComponents};
        RowCount = UpdateRegionHeight;
    }
    DEV_CHECK_ERR(SubresData.Stride >= RowSize, "Source data stride (", SubresData.Stride, ") is below the image row size (", RowSize, ").");
    const Uint32 PlaneSize = SubresData.Stride * RowCount;
    DEV_CHECK_ERR(UpdateRegionDepth == 1 || SubresData.DepthStride >= PlaneSize, "Source data depth stride (", SubresData.DepthStride, ") is below the image plane size (", PlaneSize, ").");
#endif
}

void ValidateCopyTextureParams(const CopyTextureAttribs& CopyAttribs)
{
    VERIFY_EXPR(CopyAttribs.pSrcTexture != nullptr && CopyAttribs.pDstTexture != nullptr);
    Box         SrcBox;
    const auto& SrcTexDesc = CopyAttribs.pSrcTexture->GetDesc();
    const auto& DstTexDesc = CopyAttribs.pDstTexture->GetDesc();
    auto        pSrcBox    = CopyAttribs.pSrcBox;
    if (pSrcBox == nullptr)
    {
        auto MipLevelAttribs = GetMipLevelProperties(SrcTexDesc, CopyAttribs.SrcMipLevel);
        SrcBox.MaxX          = MipLevelAttribs.LogicalWidth;
        SrcBox.MaxY          = MipLevelAttribs.LogicalHeight;
        SrcBox.MaxZ          = MipLevelAttribs.Depth;
        pSrcBox              = &SrcBox;
    }
    ValidateTextureRegion(SrcTexDesc, CopyAttribs.SrcMipLevel, CopyAttribs.SrcSlice, *pSrcBox);

    Box DstBox;
    DstBox.MinX = CopyAttribs.DstX;
    DstBox.MinY = CopyAttribs.DstY;
    DstBox.MinZ = CopyAttribs.DstZ;
    DstBox.MaxX = DstBox.MinX + (pSrcBox->MaxX - pSrcBox->MinX);
    DstBox.MaxY = DstBox.MinY + (pSrcBox->MaxY - pSrcBox->MinY);
    DstBox.MaxZ = DstBox.MinZ + (pSrcBox->MaxZ - pSrcBox->MinZ);
    ValidateTextureRegion(DstTexDesc, CopyAttribs.DstMipLevel, CopyAttribs.DstSlice, DstBox);
}

void ValidateMapTextureParams(const TextureDesc& TexDesc,
                              Uint32             MipLevel,
                              Uint32             ArraySlice,
                              MAP_TYPE           MapType,
                              Uint32             MapFlags,
                              const Box*         pMapRegion)
{
    VERIFY_TEX_PARAMS(MipLevel < TexDesc.MipLevels, "Mip level (", MipLevel, ") is out of allowed range [0, ", TexDesc.MipLevels - 1, "].");
    if (TexDesc.Type == RESOURCE_DIM_TEX_1D_ARRAY ||
        TexDesc.Type == RESOURCE_DIM_TEX_2D_ARRAY ||
        TexDesc.Type == RESOURCE_DIM_TEX_CUBE ||
        TexDesc.Type == RESOURCE_DIM_TEX_CUBE_ARRAY)
    {
        VERIFY_TEX_PARAMS(ArraySlice < TexDesc.ArraySize, "Array slice (", ArraySlice, ") is out of range [0,", TexDesc.ArraySize - 1, "].");
    }
    else
    {
        VERIFY_TEX_PARAMS(ArraySlice == 0, "Array slice (", ArraySlice, ") must be 0 for non-array textures.");
    }

    if (pMapRegion != nullptr)
    {
        ValidateTextureRegion(TexDesc, MipLevel, ArraySlice, *pMapRegion);
    }
}

void ValidatedAndCorrectTextureViewDesc(const TextureDesc& TexDesc, TextureViewDesc& ViewDesc) noexcept(false)
{
#define TEX_VIEW_VALIDATION_ERROR(...) LOG_ERROR_AND_THROW("\n                 Failed to create texture view '", (ViewDesc.Name ? ViewDesc.Name : ""), "' for texture '", TexDesc.Name, "': ", ##__VA_ARGS__)

    if (!(ViewDesc.ViewType > TEXTURE_VIEW_UNDEFINED && ViewDesc.ViewType < TEXTURE_VIEW_NUM_VIEWS))
        TEX_VIEW_VALIDATION_ERROR("Texture view type is not specified.");

    if (ViewDesc.MostDetailedMip >= TexDesc.MipLevels)
        TEX_VIEW_VALIDATION_ERROR("Most detailed mip (", ViewDesc.MostDetailedMip, ") is out of range. The texture has only ", TexDesc.MipLevels, " mip ", (TexDesc.MipLevels > 1 ? "levels." : "level."));

    if (ViewDesc.NumMipLevels != REMAINING_MIP_LEVELS && ViewDesc.MostDetailedMip + ViewDesc.NumMipLevels > TexDesc.MipLevels)
        TEX_VIEW_VALIDATION_ERROR("Most detailed mip (", ViewDesc.MostDetailedMip, ") and number of mip levels in the view (", ViewDesc.NumMipLevels, ") is out of range. The texture has only ", TexDesc.MipLevels, " mip ", (TexDesc.MipLevels > 1 ? "levels." : "level."));

    if (ViewDesc.Format == TEX_FORMAT_UNKNOWN)
        ViewDesc.Format = GetDefaultTextureViewFormat(TexDesc.Format, ViewDesc.ViewType, TexDesc.BindFlags);

    if (ViewDesc.TextureDim == RESOURCE_DIM_UNDEFINED)
    {
        if (TexDesc.Type == RESOURCE_DIM_TEX_CUBE || TexDesc.Type == RESOURCE_DIM_TEX_CUBE_ARRAY)
        {
            switch (ViewDesc.ViewType)
            {
                case TEXTURE_VIEW_SHADER_RESOURCE:
                    ViewDesc.TextureDim = TexDesc.Type;
                    break;

                case TEXTURE_VIEW_RENDER_TARGET:
                case TEXTURE_VIEW_DEPTH_STENCIL:
                case TEXTURE_VIEW_UNORDERED_ACCESS:
                    ViewDesc.TextureDim = RESOURCE_DIM_TEX_2D_ARRAY;
                    break;

                default: UNEXPECTED("Unexpected view type");
            }
        }
        else
        {
            ViewDesc.TextureDim = TexDesc.Type;
        }
    }

    switch (TexDesc.Type)
    {
        case RESOURCE_DIM_TEX_1D:
            if (ViewDesc.TextureDim != RESOURCE_DIM_TEX_1D)
            {
                TEX_VIEW_VALIDATION_ERROR("Incorrect texture type for Texture 1D view: only Texture 1D is allowed.");
            }
            break;

        case RESOURCE_DIM_TEX_1D_ARRAY:
            if (ViewDesc.TextureDim != RESOURCE_DIM_TEX_1D_ARRAY &&
                ViewDesc.TextureDim != RESOURCE_DIM_TEX_1D)
            {
                TEX_VIEW_VALIDATION_ERROR("Incorrect view type for Texture 1D Array: only Texture 1D or Texture 1D Array are allowed.");
            }
            break;

        case RESOURCE_DIM_TEX_2D:
            if (ViewDesc.TextureDim != RESOURCE_DIM_TEX_2D_ARRAY &&
                ViewDesc.TextureDim != RESOURCE_DIM_TEX_2D)
            {
                TEX_VIEW_VALIDATION_ERROR("Incorrect texture type for Texture 2D view: only Texture 2D or Texture 2D Array are allowed.");
            }
            break;

        case RESOURCE_DIM_TEX_2D_ARRAY:
            if (ViewDesc.TextureDim != RESOURCE_DIM_TEX_2D_ARRAY &&
                ViewDesc.TextureDim != RESOURCE_DIM_TEX_2D)
            {
                TEX_VIEW_VALIDATION_ERROR("Incorrect texture type for Texture 2D Array view: only Texture 2D or Texture 2D Array are allowed.");
            }
            break;

        case RESOURCE_DIM_TEX_3D:
            if (ViewDesc.TextureDim != RESOURCE_DIM_TEX_3D)
            {
                TEX_VIEW_VALIDATION_ERROR("Incorrect texture type for Texture 3D view: only Texture 3D is allowed.");
            }
            break;

        case RESOURCE_DIM_TEX_CUBE:
            if (ViewDesc.ViewType == TEXTURE_VIEW_SHADER_RESOURCE)
            {
                if (ViewDesc.TextureDim != RESOURCE_DIM_TEX_2D &&
                    ViewDesc.TextureDim != RESOURCE_DIM_TEX_2D_ARRAY &&
                    ViewDesc.TextureDim != RESOURCE_DIM_TEX_CUBE)
                {
                    TEX_VIEW_VALIDATION_ERROR("Incorrect texture type for Texture cube SRV: Texture 2D, Texture 2D array or Texture Cube is allowed.");
                }
            }
            else
            {
                if (ViewDesc.TextureDim != RESOURCE_DIM_TEX_2D &&
                    ViewDesc.TextureDim != RESOURCE_DIM_TEX_2D_ARRAY)
                {
                    TEX_VIEW_VALIDATION_ERROR("Incorrect texture type for Texture cube non-shader resource view: Texture 2D or Texture 2D array is allowed.");
                }
            }
            break;

        case RESOURCE_DIM_TEX_CUBE_ARRAY:
            if (ViewDesc.ViewType == TEXTURE_VIEW_SHADER_RESOURCE)
            {
                if (ViewDesc.TextureDim != RESOURCE_DIM_TEX_2D &&
                    ViewDesc.TextureDim != RESOURCE_DIM_TEX_2D_ARRAY &&
                    ViewDesc.TextureDim != RESOURCE_DIM_TEX_CUBE &&
                    ViewDesc.TextureDim != RESOURCE_DIM_TEX_CUBE_ARRAY)
                {
                    TEX_VIEW_VALIDATION_ERROR("Incorrect texture type for Texture cube array SRV: Texture 2D, Texture 2D array, Texture Cube or Texture Cube Array is allowed.");
                }
            }
            else
            {
                if (ViewDesc.TextureDim != RESOURCE_DIM_TEX_2D &&
                    ViewDesc.TextureDim != RESOURCE_DIM_TEX_2D_ARRAY)
                {
                    TEX_VIEW_VALIDATION_ERROR("Incorrect texture type for Texture cube array non-shader resource view: Texture 2D or Texture 2D array is allowed.");
                }
            }
            break;

        default:
            UNEXPECTED("Unexpected texture type");
            break;
    }

    if (ViewDesc.TextureDim == RESOURCE_DIM_TEX_CUBE)
    {
        if (ViewDesc.ViewType != TEXTURE_VIEW_SHADER_RESOURCE)
            TEX_VIEW_VALIDATION_ERROR("Unexpected view type: SRV is expected.");
        if (ViewDesc.NumArraySlices != 6 && ViewDesc.NumArraySlices != 0 && ViewDesc.NumArraySlices != REMAINING_ARRAY_SLICES)
            TEX_VIEW_VALIDATION_ERROR("Texture cube SRV is expected to have 6 array slices, while ", ViewDesc.NumArraySlices, " is provided.");
        if (ViewDesc.FirstArraySlice != 0)
            TEX_VIEW_VALIDATION_ERROR("First slice (", ViewDesc.FirstArraySlice, ") must be 0 for non-array texture cube SRV.");
    }
    if (ViewDesc.TextureDim == RESOURCE_DIM_TEX_CUBE_ARRAY)
    {
        if (ViewDesc.ViewType != TEXTURE_VIEW_SHADER_RESOURCE)
            TEX_VIEW_VALIDATION_ERROR("Unexpected view type: SRV is expected.");
        if (ViewDesc.NumArraySlices != REMAINING_ARRAY_SLICES && (ViewDesc.NumArraySlices % 6) != 0)
            TEX_VIEW_VALIDATION_ERROR("Number of slices in texture cube array SRV is expected to be multiple of 6. ", ViewDesc.NumArraySlices, " slices is provided.");
    }

    if (ViewDesc.TextureDim == RESOURCE_DIM_TEX_1D ||
        ViewDesc.TextureDim == RESOURCE_DIM_TEX_2D)
    {
        if (ViewDesc.FirstArraySlice != 0)
            TEX_VIEW_VALIDATION_ERROR("First slice (", ViewDesc.FirstArraySlice, ") must be 0 for non-array texture 1D/2D views.");

        if (ViewDesc.NumArraySlices != REMAINING_ARRAY_SLICES && ViewDesc.NumArraySlices > 1)
            TEX_VIEW_VALIDATION_ERROR("Number of slices in the view (", ViewDesc.NumArraySlices, ") must be 1 (or 0) for non-array texture 1D/2D views.");
    }
    else if (ViewDesc.TextureDim == RESOURCE_DIM_TEX_1D_ARRAY ||
             ViewDesc.TextureDim == RESOURCE_DIM_TEX_2D_ARRAY ||
             ViewDesc.TextureDim == RESOURCE_DIM_TEX_CUBE ||
             ViewDesc.TextureDim == RESOURCE_DIM_TEX_CUBE_ARRAY)
    {
        if (ViewDesc.FirstArraySlice >= TexDesc.ArraySize)
            TEX_VIEW_VALIDATION_ERROR("First array slice (", ViewDesc.FirstArraySlice, ") exceeds the number of slices in the texture array (", TexDesc.ArraySize, ").");

        if (ViewDesc.NumArraySlices != REMAINING_ARRAY_SLICES && ViewDesc.FirstArraySlice + ViewDesc.NumArraySlices > TexDesc.ArraySize)
            TEX_VIEW_VALIDATION_ERROR("First slice (", ViewDesc.FirstArraySlice, ") and number of slices in the view (", ViewDesc.NumArraySlices, ") specify more slices than target texture has (", TexDesc.ArraySize, ").");
    }
    else if (ViewDesc.TextureDim == RESOURCE_DIM_TEX_3D)
    {
        auto MipDepth = TexDesc.Depth >> ViewDesc.MostDetailedMip;
        if (ViewDesc.FirstDepthSlice + ViewDesc.NumDepthSlices > MipDepth)
            TEX_VIEW_VALIDATION_ERROR("First slice (", ViewDesc.FirstDepthSlice, ") and number of slices in the view (", ViewDesc.NumDepthSlices, ") specify more slices than target 3D texture mip level has (", MipDepth, ").");
    }
    else
    {
        UNEXPECTED("Unexpected texture dimension");
    }

    if (GetTextureFormatAttribs(ViewDesc.Format).IsTypeless)
    {
        TEX_VIEW_VALIDATION_ERROR("Texture view format (", GetTextureFormatAttribs(ViewDesc.Format).Name, ") cannot be typeless.");
    }

    if ((ViewDesc.Flags & TEXTURE_VIEW_FLAG_ALLOW_MIP_MAP_GENERATION) != 0)
    {
        if ((TexDesc.MiscFlags & MISC_TEXTURE_FLAG_GENERATE_MIPS) == 0)
            TEX_VIEW_VALIDATION_ERROR("TEXTURE_VIEW_FLAG_ALLOW_MIP_MAP_GENERATION flag can only set if the texture was created with MISC_TEXTURE_FLAG_GENERATE_MIPS flag.");

        if (ViewDesc.ViewType != TEXTURE_VIEW_SHADER_RESOURCE)
            TEX_VIEW_VALIDATION_ERROR("TEXTURE_VIEW_FLAG_ALLOW_MIP_MAP_GENERATION flag can only be used with TEXTURE_VIEW_SHADER_RESOURCE view type.");
    }

#undef TEX_VIEW_VALIDATION_ERROR

    if (ViewDesc.NumMipLevels == 0 || ViewDesc.NumMipLevels == REMAINING_MIP_LEVELS)
    {
        if (ViewDesc.ViewType == TEXTURE_VIEW_SHADER_RESOURCE)
            ViewDesc.NumMipLevels = TexDesc.MipLevels - ViewDesc.MostDetailedMip;
        else
            ViewDesc.NumMipLevels = 1;
    }

    if (ViewDesc.NumArraySlices == 0 || ViewDesc.NumArraySlices == REMAINING_ARRAY_SLICES)
    {
        if (ViewDesc.TextureDim == RESOURCE_DIM_TEX_1D_ARRAY ||
            ViewDesc.TextureDim == RESOURCE_DIM_TEX_2D_ARRAY ||
            ViewDesc.TextureDim == RESOURCE_DIM_TEX_CUBE ||
            ViewDesc.TextureDim == RESOURCE_DIM_TEX_CUBE_ARRAY)
            ViewDesc.NumArraySlices = TexDesc.ArraySize - ViewDesc.FirstArraySlice;
        else if (ViewDesc.TextureDim == RESOURCE_DIM_TEX_3D)
        {
            auto MipDepth           = TexDesc.Depth >> ViewDesc.MostDetailedMip;
            ViewDesc.NumDepthSlices = MipDepth - ViewDesc.FirstDepthSlice;
        }
        else
            ViewDesc.NumArraySlices = 1;
    }

    if ((ViewDesc.ViewType == TEXTURE_VIEW_RENDER_TARGET) &&
        (ViewDesc.Format == TEX_FORMAT_R8_SNORM || ViewDesc.Format == TEX_FORMAT_RG8_SNORM || ViewDesc.Format == TEX_FORMAT_RGBA8_SNORM ||
         ViewDesc.Format == TEX_FORMAT_R16_SNORM || ViewDesc.Format == TEX_FORMAT_RG16_SNORM || ViewDesc.Format == TEX_FORMAT_RGBA16_SNORM))
    {
        const auto* FmtName = GetTextureFormatAttribs(ViewDesc.Format).Name;
        LOG_WARNING_MESSAGE(FmtName, " render target view is created.\n"
                                     "There might be an issue in OpenGL driver on NVidia hardware: when rendering to SNORM textures, all negative values are clamped to zero.\n"
                                     "Use UNORM format instead.");
    }
}

} // namespace Diligent
