Skip to content

MipMapCount == 0 ? #43

Description

This is a question.

When converting textures to DDS using texconv, if you don't want mipmaps, you must specify -m 1. If you specify -m 0, the entire chain is generated and saved.

In the wild it is not uncommon to find DDS' which do not set DDS_HEADER_FLAGS_MIPMAP, and which set dwMipMapCount to 0. DirectXTex seems to want to force the behaviour of always setting that flag, and always having a non-zero value for dwMipMapCount

In DirectXTexDDS.cpp, in DecodeDDSHeader, if dwMipMapCount is 0, it is forced to 1. Then, in _EncodeDDSHeader, if dwMipMapCount is > 0, the DDS_HEADER_FLAGS_MIPMAP flag is set.

Should I take it as read, then, that mipmap-less (no flag, and dwMipMapCount == 0) is a deprecated DDS configuration?

Activity

  1. mikeeheler commented on Dec 20, 2016

    @mikeeheler
    Author

    Adding to this I have also noticed the DDS_HEADER.dwCaps DDSCAPS_MIPMAP flag. DirectXTexDDS.cpp sets this flag if dwMipMapCount is > 1, a truly mipmapped texture, while it basically always sets DDS_HEADER_FLAGS_MIPMAP and sets DDS_HEADER.dwMipMapCount to 1 or more.

  2. walbourn commented on Dec 21, 2016

    @walbourn
    Collaborator

    The DDS file format is not particularly well specified. I have made sure the basics are on MSDN, but it's largely a matter of trying to match "What does the legacy D3DX9, D3DX10, and/or D3DX11 library do?". In some places where the original behavior was a little lose, I tried to be a bit more consistent with DirectXTex.

    In the original D3DX9 code it writes DDS headers as follows:

    DDS_HEADER Header;
    memset(&Header, 0x00, sizeof(DDS_HEADER));
    
    Header.dwSize   = sizeof(DDS_HEADER);
    Header.dwFlags  = DDS_DDSD_HEIGHT | DDS_DDSD_WIDTH | DDS_DDSD_PIXELFORMAT | DDS_DDSD_CAPS;
    Header.dwHeight = (DWORD) m_uHeight;
    Header.dwWidth  = (DWORD) m_uWidth;
    Header.ddpf     = pFormatDDPF->ddpf;
    Header.dwCaps   = DDS_CAPS_TEXTURE;
    
    if(pFormatDDPF->ddpf.dwFlags & (DDS_DDPF_ALPHAPIXELS | DDS_DDPF_ALPHA))
        Header.dwCaps |= DDS_CAPS_ALPHA;
    
    if(pFormatDDPF->ddpf.dwFlags & (DDS_DDPF_PALETTEINDEXED8))
        Header.dwCaps |= DDS_CAPS_PALETTE;
    
    if(m_pMip)
    {
        Header.dwFlags |= DDS_DDSD_MIPMAPCOUNT;
        Header.dwMipMapCount = (DWORD) uMipLevels;
        Header.dwCaps |= DDS_CAPS_COMPLEX | DDS_CAPS_MIPMAP;
    }
    
    if(m_pFace)
    {
        Header.dwCaps |= DDS_CAPS_COMPLEX;
        Header.dwCaps2 |= DDS_CAPS2_CUBEMAP | DDS_CAPS2_CUBEMAP_ALLFACES;
    }
    
    if(m_uDepth > 1)
    {
        Header.dwFlags |= DDS_DDSD_DEPTH;
        Header.dwDepth = (DWORD) m_uDepth;
        Header.dwCaps2 |= DDS_CAPS2_VOLUME;
    }
    

    When reading DDS files, D3DX9 does this with respect to the mipmaps (it reads dwMipMapCount whether or not the flags included DDS_DDSD_MIPMAPCOUNT, treating 0 as 1).

    // Grok levels
    UINT cMipLevels;
    cMipLevels = (UINT) pHeader->dwMipMapCount;
    
    if(0 == cMipLevels)
        cMipLevels = 1
    

    Therefore that's the behavior I implemented for DirectXTex.

    The writing code was rewritten for D3DX10 (and carried forward to D3DX11) to add the DX10 extension header but it does the same basic thing. Here's writing a DDS header (you'll note it doesn't bother setting DDS_DDSD_MIPMAPCOUNT at all).

    ZeroMemory(&fileHeader, sizeof(fileHeader));
    fileHeader.dwMagic = MAKEFOURCC('D', 'D', 'S', ' ');
    fileHeader.header.ddpf = pFormat->ddpf;
    fileHeader.header.dwCaps = DDS_CAPS_TEXTURE;
    if (m_cMipmaps > 1)
        fileHeader.header.dwCaps |= DDS_CAPS_MIPMAP;
    
    fileHeader.header.dwFlags |= DDS_DDSD_HEIGHT | DDS_DDSD_WIDTH;
    fileHeader.header.dwWidth = m_Width;
    fileHeader.header.dwHeight = m_Height;
    fileHeader.header.dwLinearSize = m_Stride;
    fileHeader.header.dwDepth = m_Depth;
    fileHeader.header.dwMipMapCount = m_cMipmaps;
    fileHeader.header.dwSize = sizeof(DDS_HEADER);
    
    switch(m_ResType)
    {
    case D3D10_RESOURCE_DIMENSION_TEXTURE3D:
        fileHeader.header.dwFlags |= DDS_DDSD_DEPTH;
        fileHeader.header.dwCaps |= DDS_CAPS_COMPLEX;
        fileHeader.header.dwCaps2 |= DDS_CAPS2_VOLUME;
        break;
    
    case D3D10_RESOURCE_DIMENSION_TEXTURE2D:
        if (m_bIsCubemap)
        {
            fileHeader.header.dwCaps |= DDS_CAPS_COMPLEX; 
            fileHeader.header.dwCaps2 |= DDS_CAPS2_CUBEMAP | DDS_CAPS2_CUBEMAP_ALLFACES;
        }
        break;
    
    case D3D10_RESOURCE_DIMENSION_TEXTURE1D:
        break;
    
    default:
        VNASSERT(0);
        break;
    }
    

    The D3DX10 and D3DX11 header parsing code is the same as it was in D3DX9 with respect to the mipmapcount.

    The DirectXTex texconv behavior is also based on the legacy DirectX SDK command-line tool of the same name, which is why it defaults to generating all mipmaps (i.e. -m 0) when writing out a DDS file.

  3. mikeeheler commented on Dec 21, 2016

    @mikeeheler
    Author

    Great! Thank you for the thorough response, Chuck. It's very appreciated.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions