尝试解析DDS格式纹理文件的基本信息(不包括图像数据)

目标

DDS(DirectDraw Surface)是微软为DirectX准备的一种纹理格式。我注意到可以在VisualStudio中预览:
在这里插入图片描述
我当前关注这种纹理格式,是因为:

  1. 不管是D3D11的官方范例(在Microsoft DirectX SDK (June 2010)中),还是D3D12的官方范例(microsoft/DirectX-Graphics-Samples),其中的纹理资源都是DDS格式(DX12龙书中的范例也用DDS)。范例代码直接使用了DDS专用的接口将数据读入并转换为DX资源,这个过程不透明。然而,我在写自己的图形API学习工程时,我需要非DDS格式的纹理。因此,我需要了解DDS被解析并转换为DX资源的细节,以便能将其替换为其他格式。
  2. 对于立方体纹理(TextureCube),我在VisualStudio中似乎并不能看到所有的6个面,甚至连它是不是立方体纹理都不知道。同样,它转变为DX资源的细节也需要研究。

因此,我决定尝试用底层的方法对DDS文件进行解析,参考代码:

我本来的目标是:

  • 参考官方代码,一步步地获得一个DDS文件的基本信息(比如是不是立方体纹理),最后得到原始纹理数据。作为预览测试,我将最后得到的纹理数据创建为bmp图像

实际发现,由于牵扯到 压缩(Texture Block Compression in Direct3D 11)还有非8位的像素数据(HDR)。得到纹理的数据并将其转变为bmp是个相对复杂的事情(详细见Programming Guide for DDS - Win32 apps | Microsoft Docs)。因此,最后我只做了解析文件头的工作。

简单分析官方的代码

Microsoft DirectX SDK (June 2010)中的【DDSWithoutD3DX11】的DDSTextureLoader.h可以看到:
在这里插入图片描述
它对外分别为DX9与DX11提供了一个接口,将DDS文件转变为DX的资源。

内部,这个函数主要分为两部分:
在这里插入图片描述

  1. 使用LoadTextureDataFromFile将数据读到pHeaderpBitData
  2. 使用上一步得到数据作为参数调用CreateTextureFromDDS获得DX资源。

microsoft/DirectX-Graphics-Samples中的DDSTextureLoader11.cpp中代码已有较大变化,但是主要部分还是这两个:
在这里插入图片描述

0. 代码初步结构

仿照官方代码,初步结构如下:

#include <iostream>

#include <Windows.h>

#include "DDS.h"

#include "HRESULTStrings.h"

using namespace DirectX;

//从文件中读取DDS的文件头与图像数据
HRESULT LoadTextureDataFromFile(
    const wchar_t* fileName,        //dds文件名
    BYTE** fileDataBuffer,          //容纳文件数据的Buffer指针
    const DDS_HEADER** header,      //dds文件头
    const uint8_t** bitData,        //图像数据
    size_t* bitSize                 //图像数据的尺寸
) 
{
    return S_OK;
}

//根据DDS的数据解析出基本信息
HRESULT LogBasicDataFromDDS(
    const DDS_HEADER* header,       //dds文件头
    const uint8_t* bitData,         //图像数据
    size_t bitSize,                 //图像数据的尺寸
    const wchar_t* outputPath_noExt //输出的文件名(不带后缀)
)
{
    return S_OK;
}


int main()
{
    //测试读取的dds文件名
    const wchar_t* ddsFileName = L"D:/Temp/uffizi_cross.dds";
    //输出的路径
    const wchar_t* outputPath = L"D:/Temp/output";

    std::wcout << "尝试读取文件:" << ddsFileName << std::endl;

    //DDS的数据:
    BYTE* fileDataBuffer = NULL;        //容纳文件数据的Buffer         
    const DDS_HEADER* header = nullptr; //指向dds文件头的指针
    const uint8_t* bitData = nullptr;   //指向图像数据的指针
    size_t bitSize = 0;                 //图像数据的尺寸

   //从文件中读取DDS的文件头与图像数据
    HRESULT hr = LoadTextureDataFromFile(ddsFileName, &fileDataBuffer, &header, &bitData, &bitSize);

    if (hr != S_OK)//读取失败:
    {
        std::cout << "LoadTextureDataFromFile失败" << std::endl;

        //输出hr代表的信息
        std::cout << GetHRESULTMessage(hr);

        //释放Buffer
        if (fileDataBuffer)
            delete[] fileDataBuffer;

        return 0;
    }


    //根据DDS的数据解析出基本信息
    hr = LogBasicDataFromDDS(header, bitData, bitSize, outputPath);

    if (hr != S_OK)//创建失败:
    {
        std::cout << "LogBasicDataFromDDS失败" << std::endl;

        //输出hr代表的信息
        std::cout << GetHRESULTMessage(hr);
    }

    //释放Buffer
    if (fileDataBuffer)
        delete[] fileDataBuffer;

    return 0;
}

其中,DDS.h来源于官方范例DirectXTex/DirectXTex/DDS.h。(不过需要补充上一个#include <dxgiformat.h>
在这里插入图片描述
HRESULTStrings.h来源于我之前的博客《制作一个小工具:自动生成“获得特定HRESULT对应信息的函数”的代码》。它提供了一个函数GetHRESULTMessage可以将HRESULT转变为字符串信息。

1. 从文件中读取DDS的文件头与图像数据

下面深入LoadTextureDataFromFile函数:

1.1 打开文件

    //打开文件
#if (_WIN32_WINNT >= _WIN32_WINNT_WIN8)
    HANDLE hFile = (CreateFile2(fileName,
        GENERIC_READ,
        FILE_SHARE_READ,
        OPEN_EXISTING,
        nullptr));
#else
    HANDLE hFile = (CreateFileW(fileName,
        GENERIC_READ,
        FILE_SHARE_READ,
        nullptr,
        OPEN_EXISTING,
        FILE_ATTRIBUTE_NORMAL,
        nullptr));
#endif
    if (INVALID_HANDLE_VALUE == hFile)  //打开文件失败
        return GetLastError();

官方似乎还对Windows版本做了区分,我这边也将其逻辑搬了过来

1.2 获得文件信息并检查文件的尺寸

 //获得文件信息
FILE_STANDARD_INFO fileInfo;
if (!GetFileInformationByHandleEx(hFile, FileStandardInfo, &fileInfo, sizeof(fileInfo)))
    return HRESULT_FROM_WIN32(GetLastError());

//检查文件尺寸
{
    //文件太大了,拒绝读取
    if (fileInfo.EndOfFile.HighPart > 0)
        return E_FAIL;

    //文件太小了,一定不是一个有效的DDS文件
    //DDS文件开头是一个魔法数(uint32_t)加DDS_HEADER,因此尺寸一定比它大
    if (fileInfo.EndOfFile.LowPart < (sizeof(uint32_t) + sizeof(DDS_HEADER)))
        return E_FAIL;

    std::cout << "文件尺寸合格,大小是:" << fileInfo.EndOfFile.LowPart << std::endl;
}

1.3 读取数据

//读取文件中的数据到fileDataBuffer中
{
    //创建足够的空间可读取文件数据
    *fileDataBuffer = new BYTE[fileInfo.EndOfFile.LowPart];
    if (!fileDataBuffer)//这里创建失败是因为超过内存限制
        return E_OUTOFMEMORY;
    //读取数据
    DWORD bytesRead = 0;    //读了多少BYTE
    if (!ReadFile(hFile, *fileDataBuffer, fileInfo.EndOfFile.LowPart, &bytesRead, nullptr))
    {
        delete[] fileDataBuffer;
        return GetLastError();
    }
    //如果读取的BYTE数目小于文件大小,则有问题
    if (bytesRead < fileInfo.EndOfFile.LowPart)
    {
        delete[] fileDataBuffer;
        return E_FAIL;
    }

    std::cout << "读取文件数据成功" << std::endl;
}

1.4 检查魔法数字

DDS.h中定义了一个魔法数字,此数字的值就是4个char:“DDS”加一个空格。
在这里插入图片描述
下面检查一下所读文件开头是不是它

//DDS文件一定会以一个魔法数字("DDS ")做开头,检查一下
{
    auto dwMagicNumber = *reinterpret_cast<const uint32_t*>(*fileDataBuffer);
    if (dwMagicNumber != DDS_MAGIC)
    {
        delete[] fileDataBuffer;
        return E_FAIL;
    }

    std::cout << "开头确实是“DDS ”" << std::endl;
}

1.5 获得文件头并检查

//文件头的位置是:文件初始位置 + 魔法数字("DDS ")的偏移
auto header_data = reinterpret_cast<const DDS_HEADER*>(*fileDataBuffer + sizeof(uint32_t));
//检查:
{
    //验证文件头是否有效
    if (header_data->size != sizeof(DDS_HEADER) ||
        header_data->ddspf.size != sizeof(DDS_PIXELFORMAT))
    {
        delete[] fileDataBuffer;
        return E_FAIL;
    }

     std::cout << "通过文件头的 size 和 ddspf.size 判断是一个有效的文件头"<<std::endl;
}

1.6 检查是否有DX10扩展

//检查是否有DX10扩展
bool bDXT10Header = false;
{
    //FOURCC: Four-character codes,四个字符的编码

    if ((header_data->ddspf.flags & DDS_FOURCC) &&  //ddspf.flags有DDS_FOURCC说明ddspf.fourCC有效
        //检查ddspf.fourCC的值是不是DX10
        (MAKEFOURCC('D', 'X', '1', '0') == header_data->ddspf.fourCC))
    {
        // 现在,文件的大小至少是: 魔法数字 + DDS_HEADER + DDS_HEADER_DXT10 了
        if (fileInfo.EndOfFile.LowPart < (sizeof(uint32_t) + sizeof(DDS_HEADER) + sizeof(DDS_HEADER_DXT10)))
        {
            delete[] fileDataBuffer;
            return E_FAIL;
        }

        bDXT10Header = true;
    }

    std::cout << "是否有DX10扩展:" << (bDXT10Header ? "true" : "false") << std::endl;
}

首先,代码中的术语 “FOURCC” 指的是:Four-character codes,四字符的编码。
DDS_HEADER 中的 DDS_PIXELFORMAT ddspfdwFlags,其中一个flag DDPF_FOURCC 标志着它的fourCC成员是否包含一个有效的数据
在这里插入图片描述
如果包含,则接下来会检查fourCC的值是不是正好等于'D', 'X', '1', '0',如果是则表明它是一个DX10扩展。

1.7 最终返回所有需要的DDS数据

//最终返回所有读取到的DDS数据
{
    //文件头
    *header = header_data;

    //图像数据的偏移
    uint32_t bitData_offset =
        sizeof(uint32_t)                                    //魔法数字
        + sizeof(DDS_HEADER)                                //DDS文件头
        + (bDXT10Header ? sizeof(DDS_HEADER_DXT10) : 0);    //可能的DX10扩展
    //图像数据指针:
    *bitData = *fileDataBuffer + bitData_offset;

    //图像数据的尺寸
    *bitSize = fileInfo.EndOfFile.LowPart - bitData_offset;

    std::cout << "读取成功,得到图像数据的尺寸是:"<< *bitSize << std::endl;
}

2. 根据DDS的数据解析出基本信息

下面深入LogBasicDataFromDDS函数

2.1 获得图像宽高深

UINT width = header->width;     //宽度
UINT height = header->height;   //高度
UINT depth = header->depth;     //深度

std::cout << "宽度:" << width << "," << "高度:" << height << "," << "深度:" << depth << std::endl;

2.2 检查是否有DX10扩展

接下来的一些逻辑还和是否有DX10扩展有关,所以这里再检查一次

//检查是否有dx10扩展
const DDS_HEADER_DXT10* d3d10ext =
    ((header->ddspf.flags & DDS_FOURCC) && (MAKEFOURCC('D', 'X', '1', '0') == header->ddspf.fourCC)) ?
    reinterpret_cast<const DDS_HEADER_DXT10*>(reinterpret_cast<const uint8_t*>(header) + sizeof(DDS_HEADER))
    : nullptr;
std::cout << "是否有DX10扩展:" << (d3d10ext ? "true" : "false") << std::endl;

如果无扩展,则d3d10ext的值为nullptr,后续将通过此值来判断。

2.3 获得格式(DXGI_FORMAT)

//获得格式
DXGI_FORMAT format = DXGI_FORMAT_UNKNOWN;
{
    if (d3d10ext == nullptr)//如果无DX10扩展
    {
        //通过文件头的 DDS_PIXELFORMAT ddspf
        format = GetDXGIFormat(header->ddspf);

        if (format == DXGI_FORMAT_UNKNOWN) //没找到格式
            return ERROR_NOT_SUPPORTED;
    }
    else //有DX10扩展
    {
        switch (d3d10ext->dxgiFormat)
        {
        case DXGI_FORMAT_AI44:
        case DXGI_FORMAT_IA44:
        case DXGI_FORMAT_P8:
        case DXGI_FORMAT_A8P8:
            return ERROR_NOT_SUPPORTED;
        }

        //通过dx10扩展上的信息
        format = d3d10ext->dxgiFormat;
    }

    if (BitsPerPixel(format) == 0)     //格式不支持
        return ERROR_NOT_SUPPORTED;

    std::cout << "格式:" << GetEnumString_DXGI_FORMAT(format) << " 位数:" << BitsPerPixel(format) << std::endl;
}

首先需要指出,这里需要两个函数:

  • DXGI_FORMAT GetDXGIFormat(const DDS_PIXELFORMAT& ddpf)函数通过一个给定的DDS_PIXELFORMAT数据判断出实际的DXGI_FORMAT格式。
  • size_t BitsPerPixel(_In_ DXGI_FORMAT fmt)函数得到一个DXGI_FORMAT格式对应有几位(bit)。如果格式不支持则返回0。

这两个函数我都从DirectXTex/DDSTextureLoader/DDSTextureLoader11.cpp中拷贝了过来。


然后,逻辑上分为两部分:


GetEnumString_DXGI_FORMAT函数负责将格式转换为能显示的字符串,其得到的方式记录在之前的博客《制作一个小工具:自动生成“将特定枚举值转换成字符串的C++函数”的代码》中。

2.4 得到纹理的种类(普通2D贴图?立方体?3D体数据?)

关于“纹理的种类”,有下面几个变量可以描述:

uint32_t resDim = D3D11_RESOURCE_DIMENSION_UNKNOWN; //资源的维度,例如体数据是三维
UINT arraySize = 1;                                 //纹理有几张,例如立方体纹理是6张
bool isCubeMap = false;                             //是否是立方体纹理

随后,也根据是否有DX10扩展分为了两种情况:


对于无DX10扩展的情况:

if (header->flags & DDS_HEADER_FLAGS_VOLUME)//查看是否是3D纹理
{
    resDim = D3D11_RESOURCE_DIMENSION_TEXTURE3D;
}
else //不是3D纹理
{
    //caps2上有cubemap相关的信息
    if (header->caps2 & DDS_CUBEMAP)
    {
        //这里要求必须有六个面
        if ((header->caps2 & DDS_CUBEMAP_ALLFACES) != DDS_CUBEMAP_ALLFACES)
            return ERROR_NOT_SUPPORTED;                   

        arraySize = 6;
        isCubeMap = true;
    }

    depth = 1;
    resDim = D3D11_RESOURCE_DIMENSION_TEXTURE2D;

    // Note there's no way for a legacy Direct3D 9 DDS to express a '1D' texture
}

首先,它靠DDS_HEADER Flags成员来判断是否是体数据:
在这里插入图片描述
随后,它依靠DDS_HEADER caps2成员来判断是否是CubeMap:
在这里插入图片描述
DDS_CUBEMAP_ALLFACES意味着所有的六个面都在:
在这里插入图片描述


对于有DX10扩展的情况:

arraySize = d3d10ext->arraySize;

if (arraySize == 0)
    return HRESULT_FROM_WIN32(ERROR_INVALID_DATA);       

switch (d3d10ext->resourceDimension)
{
case D3D11_RESOURCE_DIMENSION_TEXTURE1D:
    // D3DX writes 1D textures with a fixed Height of 1
    if ((header->flags & DDS_HEIGHT) && height != 1)
        return ERROR_INVALID_DATA;
    
    height = depth = 1;
    break;

case D3D11_RESOURCE_DIMENSION_TEXTURE2D:
    if (d3d10ext->miscFlag & D3D11_RESOURCE_MISC_TEXTURECUBE)
    {
        arraySize *= 6;
        isCubeMap = true;
    }
    depth = 1;
    break;

case D3D11_RESOURCE_DIMENSION_TEXTURE3D:
    if (!(header->flags & DDS_HEADER_FLAGS_VOLUME))
        return ERROR_INVALID_DATA;
    
    if (arraySize > 1)
        return ERROR_NOT_SUPPORTED;
    
    break;

default:
    return ERROR_NOT_SUPPORTED;
}

resDim = d3d10ext->resourceDimension;

它依靠DDS_HEADER_DXT10miscFlag成员来判断是否是一个CubeMap:
在这里插入图片描述

完整代码

#include <iostream>

#include <Windows.h>

#include<d3d11.h>

#include "DDS.h"
#include "DDSHelper.h"

#include "HRESULTStrings.h"
#include "GetEnumString_DXGI_FORMAT.h"
#include "GetEnumString_D3D11_RESOURCE_DIMENSION.h"

using namespace DirectX;

//从文件中读取DDS的文件头与图像数据
HRESULT LoadTextureDataFromFile(
    const wchar_t* fileName,        //dds文件名
    BYTE** fileDataBuffer,          //容纳文件数据的Buffer指针
    const DDS_HEADER** header,      //dds文件头
    const uint8_t** bitData,        //图像数据
    size_t* bitSize                 //图像数据的尺寸
) 
{
    //打开文件
#if (_WIN32_WINNT >= _WIN32_WINNT_WIN8)
    HANDLE hFile = (CreateFile2(fileName,
        GENERIC_READ,
        FILE_SHARE_READ,
        OPEN_EXISTING,
        nullptr));
#else
    HANDLE hFile = (CreateFileW(fileName,
        GENERIC_READ,
        FILE_SHARE_READ,
        nullptr,
        OPEN_EXISTING,
        FILE_ATTRIBUTE_NORMAL,
        nullptr));
#endif
    if (INVALID_HANDLE_VALUE == hFile)  //打开文件失败
        return GetLastError();

    //获得文件信息
    FILE_STANDARD_INFO fileInfo;
    if (!GetFileInformationByHandleEx(hFile, FileStandardInfo, &fileInfo, sizeof(fileInfo)))
        return HRESULT_FROM_WIN32(GetLastError());
    
    //检查文件尺寸
    {
        //文件太大了,拒绝读取
        if (fileInfo.EndOfFile.HighPart > 0)
            return E_FAIL;

        //文件太小了,一定不是一个有效的DDS文件
        //DDS文件开头是一个魔法数(uint32_t)加DDS_HEADER,因此尺寸一定比它大
        if (fileInfo.EndOfFile.LowPart < (sizeof(uint32_t) + sizeof(DDS_HEADER)))
            return E_FAIL;

        std::cout << "文件尺寸合格,大小是:" << fileInfo.EndOfFile.LowPart << std::endl;
    }
    
    //读取文件中的数据到fileDataBuffer中
    {
        //创建足够的空间可读取文件数据
        *fileDataBuffer = new BYTE[fileInfo.EndOfFile.LowPart];
        if (!fileDataBuffer)//这里创建失败是因为超过内存限制
            return E_OUTOFMEMORY;
        //读取数据
        DWORD bytesRead = 0;    //读了多少BYTE
        if (!ReadFile(hFile, *fileDataBuffer, fileInfo.EndOfFile.LowPart, &bytesRead, nullptr))
        {
            delete[] fileDataBuffer;
            return GetLastError();
        }
        //如果读取的BYTE数目小于文件大小,则有问题
        if (bytesRead < fileInfo.EndOfFile.LowPart)
        {
            delete[] fileDataBuffer;
            return E_FAIL;
        }

        std::cout << "读取文件数据成功" << std::endl;
    }

    //DDS文件一定会以一个魔法数字("DDS ")做开头,检查一下
    {
        auto dwMagicNumber = *reinterpret_cast<const uint32_t*>(*fileDataBuffer);
        if (dwMagicNumber != DDS_MAGIC)
        {
            delete[] fileDataBuffer;
            return E_FAIL;
        }

        std::cout << "开头确实是“DDS ”" << std::endl;
    }
    
    //文件头的位置是:文件初始位置 + 魔法数字("DDS ")的偏移
    auto header_data = reinterpret_cast<const DDS_HEADER*>(*fileDataBuffer + sizeof(uint32_t));
    //检查:
    {
        //验证文件头是否有效
        if (header_data->size != sizeof(DDS_HEADER) ||
            header_data->ddspf.size != sizeof(DDS_PIXELFORMAT))
        {
            delete[] fileDataBuffer;
            return E_FAIL;
        }

        std::cout << "通过文件头的 size 和 ddspf.size 判断是一个有效的文件头"<<std::endl;
    }

    //检查是否有DX10扩展
    bool bDXT10Header = false;
    {
        //FOURCC: Four-character codes,四个字符的编码

        if ((header_data->ddspf.flags & DDS_FOURCC) &&  //ddspf.flags有DDS_FOURCC说明ddspf.fourCC有效
            //检查ddspf.fourCC的值是不是DX10
            (MAKEFOURCC('D', 'X', '1', '0') == header_data->ddspf.fourCC))
        {
            // 现在,文件的大小至少是: 魔法数字 + DDS_HEADER + DDS_HEADER_DXT10 了
            if (fileInfo.EndOfFile.LowPart < (sizeof(uint32_t) + sizeof(DDS_HEADER) + sizeof(DDS_HEADER_DXT10)))
            {
                delete[] fileDataBuffer;
                return E_FAIL;
            }

            bDXT10Header = true;
        }

        std::cout << "是否有DX10扩展:" << (bDXT10Header ? "true" : "false") << std::endl;
    }
    
    //最终返回所有读取到的DDS数据
    {
        //文件头
        *header = header_data;

        //图像数据的偏移
        uint32_t bitData_offset =
            sizeof(uint32_t)                                    //魔法数字
            + sizeof(DDS_HEADER)                                //DDS文件头
            + (bDXT10Header ? sizeof(DDS_HEADER_DXT10) : 0);    //可能的DX10扩展
        //图像数据指针:
        *bitData = *fileDataBuffer + bitData_offset;

        //图像数据的尺寸
        *bitSize = fileInfo.EndOfFile.LowPart - bitData_offset;

        std::cout << "读取成功,得到图像数据的尺寸是:"<< *bitSize << std::endl;
    }
    

    return S_OK;
}

//根据DDS的数据解析出基本信息
HRESULT LogBasicDataFromDDS(
    const DDS_HEADER* header,       //dds文件头
    const uint8_t* bitData,         //图像数据
    size_t bitSize,                 //图像数据的尺寸
    const wchar_t* outputPath_noExt //输出的文件名(不带后缀)
)
{
    UINT width = header->width;     //宽度
    UINT height = header->height;   //高度
    UINT depth = header->depth;     //深度
    std::cout << "宽度:" << width << "," << "高度:" << height << "," << "深度:" << depth << std::endl;

    //检查是否有dx10扩展
    const DDS_HEADER_DXT10* d3d10ext =
        ((header->ddspf.flags & DDS_FOURCC) && (MAKEFOURCC('D', 'X', '1', '0') == header->ddspf.fourCC)) ?
        reinterpret_cast<const DDS_HEADER_DXT10*>(reinterpret_cast<const uint8_t*>(header) + sizeof(DDS_HEADER))
        : nullptr;
    std::cout << "是否有DX10扩展:" << (d3d10ext ? "true" : "false") << std::endl;

    //获得格式
    DXGI_FORMAT format = DXGI_FORMAT_UNKNOWN;
    {
        if (d3d10ext == nullptr)//如果无DX10扩展
        {
            //通过文件头的 DDS_PIXELFORMAT ddspf
            format = GetDXGIFormat(header->ddspf);

            if (format == DXGI_FORMAT_UNKNOWN) //没找到格式
                return ERROR_NOT_SUPPORTED;
        }
        else //有DX10扩展
        {
            switch (d3d10ext->dxgiFormat)
            {
            case DXGI_FORMAT_AI44:
            case DXGI_FORMAT_IA44:
            case DXGI_FORMAT_P8:
            case DXGI_FORMAT_A8P8:
                return ERROR_NOT_SUPPORTED;
            }

            //通过dx10扩展上的信息
            format = d3d10ext->dxgiFormat;
        }

        if (BitsPerPixel(format) == 0)     //格式不支持
            return ERROR_NOT_SUPPORTED;

        std::cout << "格式:" << GetEnumString_DXGI_FORMAT(format) << " 位数:" << BitsPerPixel(format) << std::endl;
    }

    //得到纹理的种类
    uint32_t resDim = D3D11_RESOURCE_DIMENSION_UNKNOWN; //资源的维度,例如体数据是三维
    UINT arraySize = 1;                                 //纹理有几张,例如立方体纹理是6张
    bool isCubeMap = false;                             //是否是立方体纹理
    {
        if (d3d10ext == nullptr)//如果无DX10扩展
        {
            if (header->flags & DDS_HEADER_FLAGS_VOLUME)//查看是否是3D纹理
            {
                resDim = D3D11_RESOURCE_DIMENSION_TEXTURE3D;
            }
            else //不是3D纹理
            {
                //caps2上有cubemap相关的信息
                if (header->caps2 & DDS_CUBEMAP)
                {
                    //这里要求必须有六个面
                    if ((header->caps2 & DDS_CUBEMAP_ALLFACES) != DDS_CUBEMAP_ALLFACES)
                        return ERROR_NOT_SUPPORTED;                   

                    arraySize = 6;
                    isCubeMap = true;
                }

                depth = 1;
                resDim = D3D11_RESOURCE_DIMENSION_TEXTURE2D;

                // Note there's no way for a legacy Direct3D 9 DDS to express a '1D' texture
            }
        }
        else                    //有DX10扩展
        {
            arraySize = d3d10ext->arraySize;

            if (arraySize == 0)
                return HRESULT_FROM_WIN32(ERROR_INVALID_DATA);       

            switch (d3d10ext->resourceDimension)
            {
            case D3D11_RESOURCE_DIMENSION_TEXTURE1D:
                // D3DX writes 1D textures with a fixed Height of 1
                if ((header->flags & DDS_HEIGHT) && height != 1)
                    return ERROR_INVALID_DATA;
                
                height = depth = 1;
                break;

            case D3D11_RESOURCE_DIMENSION_TEXTURE2D:
                if (d3d10ext->miscFlag & D3D11_RESOURCE_MISC_TEXTURECUBE)
                {
                    arraySize *= 6;
                    isCubeMap = true;
                }
                depth = 1;
                break;

            case D3D11_RESOURCE_DIMENSION_TEXTURE3D:
                if (!(header->flags & DDS_HEADER_FLAGS_VOLUME))
                    return ERROR_INVALID_DATA;
                
                if (arraySize > 1)
                    return ERROR_NOT_SUPPORTED;
                
                break;

            default:
                return ERROR_NOT_SUPPORTED;
            }

            resDim = d3d10ext->resourceDimension;
        }

        std::cout << "资源的维度:" << GetEnumString_D3D11_RESOURCE_DIMENSION((D3D11_RESOURCE_DIMENSION)resDim) << std::endl;
        std::cout << "纹理有几张:" << arraySize << std::endl;
        std::cout << "是否是CubeMap:" << (isCubeMap ? "true" : "false")<<std::endl;
    }

    return S_OK;
}


int main()
{
    //测试读取的dds文件名
    const wchar_t* ddsFileName = L"D:/Temp/uffizi_cross.dds";
    //输出的路径
    const wchar_t* outputPath = L"D:/Temp/output";

    std::wcout << "尝试读取文件:" << ddsFileName << std::endl;

    //DDS的数据:
    BYTE* fileDataBuffer = NULL;        //容纳文件数据的Buffer         
    const DDS_HEADER* header = nullptr; //指向dds文件头的指针
    const uint8_t* bitData = nullptr;   //指向图像数据的指针
    size_t bitSize = 0;                 //图像数据的尺寸

    //根据DDS的数据解析出基本信息
    HRESULT hr = LoadTextureDataFromFile(ddsFileName, &fileDataBuffer, &header, &bitData, &bitSize);

    if (hr != S_OK)//读取失败:
    {
        std::cout << "LoadTextureDataFromFile失败" << std::endl;

        //输出hr代表的信息
        std::cout << GetHRESULTMessage(hr);

        //释放Buffer
        if (fileDataBuffer)
            delete[] fileDataBuffer;

        return 0;
    }


    //根据DDS的数据创建Bmp图片
    hr = LogBasicDataFromDDS(header, bitData, bitSize, outputPath);

    if (hr != S_OK)//创建失败:
    {
        std::cout << "CreateBmpTextureFromDDS失败" << std::endl;

        //输出hr代表的信息
        std::cout << GetHRESULTMessage(hr);
    }

    //释放Buffer
    if (fileDataBuffer)
        delete[] fileDataBuffer;

    return 0;
}

测试

尝试读取Microsoft DirectX SDK (June 2010)中的:

  • 【DDSWithoutD3DX11】的seafloor.dds
  • 【HDRFormats】的uffizi_cross.dds
尝试读取文件:D:/Temp/seafloor.dds
文件尺寸合格,大小是:43832
读取文件数据成功
开头确实是“DDS ”
通过文件头的 size 和 ddspf.size 判断是一个有效的文件头
是否有DX10扩展:false
读取成功,得到图像数据的尺寸是:43704
宽度:256,高度:256,深度:0
是否有DX10扩展:false
格式:DXGI_FORMAT_BC1_UNORM 位数:4
资源的维度:D3D11_RESOURCE_DIMENSION_TEXTURE2D
纹理有几张:1
是否是CubeMap:false
尝试读取文件:D:/Temp/uffizi_cross.dds
文件尺寸合格,大小是:12583040
读取文件数据成功
开头确实是“DDS ”
通过文件头的 size 和 ddspf.size 判断是一个有效的文件头
是否有DX10扩展:false
读取成功,得到图像数据的尺寸是:12582912
宽度:512,高度:512,深度:0
是否有DX10扩展:false
格式:DXGI_FORMAT_R16G16B16A16_FLOAT 位数:64
资源的维度:D3D11_RESOURCE_DIMENSION_TEXTURE2D
纹理有几张:6
是否是CubeMap:true
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值