Saturday, August 22, 2015

DDS自力ロード(Cube Map, Mipmap, DXT1, DXT3, DXT5対応)

Windows10が提供されて誰でもDirectX12による開発ができるようになりました。しかし、DirectXTKやDirectXTexのようなテクスチャのロードが出来るライブラリがまだマイクロソフトから提供されていないようです。DDSは特に複雑なフォーマットではないはずなので、DX12の準備を兼ねて自力でロードを試みてみます。ただし、DirectX11です。(DX12も対応しました。

以下でほぼ全部です。キューブマップ、ミップマップ、圧縮/非圧縮すべて対応しています。エラーチェックはちゃんとしていないので壊れたファイルや未対応の形式でバッファーオーバーランなどの危険があるかもしれません。

struct DDSHeader {
uint32_t h3[3];
int h, w;
uint32_t h2[2];
int mipCnt;
uint32_t h13[13];
uint32_t fourcc, bitsPerPixel, rMask, gMask, bMask, aMask, caps1, caps2;
bool IsCubeMap() const { return caps2 == 0xFE00; }
int GetArraySize() const { return IsCubeMap() ? 6 : 1; }
int GetMipCnt() const { return std::max(mipCnt, 1); }
};
static void ArrangeRawDDS(void* img, int size)
{
const DDSHeader* hdr = (DDSHeader*)img;
DWORD rShift, gShift, bShift, aShift;
_BitScanForward(&rShift, hdr->rMask);
_BitScanForward(&gShift, hdr->gMask);
_BitScanForward(&bShift, hdr->bMask);
_BitScanForward(&aShift, hdr->aMask);
std::for_each((uint32_t*)img + 128 / 4, (uint32_t*)img + size / 4, [&](uint32_t& im) {
im = ((hdr->aMask & im) >> aShift << 24) + ((hdr->bMask & im) >> bShift << 16) + ((hdr->gMask & im) >> gShift << 8) + ((hdr->rMask & im) >> rShift);
} );
}
static ComPtr<ID3D11ShaderResourceView> LoadDDSTexture(const char* name, ivec2& texSize)
{
int size;
ComPtr<ID3D11ShaderResourceView> srv;
void* img = LoadFile(name, &size);
if (!img) {
aflog("LoadDDSTexture failed! %s", name);
return 0;
}
const DDSHeader* hdr = (DDSHeader*)img;
DXGI_FORMAT format;
int (*pitchCalcurator)(int, int) = nullptr;
switch (hdr->fourcc) {
case 0x31545844: //'1TXD':
format = DXGI_FORMAT_BC1_UNORM;
pitchCalcurator = [](int w, int h) { return ((w + 3) / 4) * ((h + 3) / 4) * 8; };
break;
case 0x33545844: //'3TXD':
format = DXGI_FORMAT_BC2_UNORM;
pitchCalcurator = [](int w, int h) { return ((w + 3) / 4) * ((h + 3) / 4) * 16; };
break;
case 0x35545844: //'5TXD':
format = DXGI_FORMAT_BC3_UNORM;
pitchCalcurator = [](int w, int h) { return ((w + 3) / 4) * ((h + 3) / 4) * 16; };
break;
default:
ArrangeRawDDS(img, size);
format = DXGI_FORMAT_R8G8B8A8_UNORM;
pitchCalcurator = [](int w, int h) { return w * h * 4; };
break;
}
texSize.x = hdr->w;
texSize.y = hdr->h;
int arraySize = hdr->GetArraySize();
int mipCnt = hdr->GetMipCnt();
CD3D11_TEXTURE2D_DESC desc(format, hdr->w, hdr->h, arraySize, hdr->GetMipCnt(), D3D11_BIND_SHADER_RESOURCE, D3D11_USAGE_DEFAULT, 0, 1, 0, hdr->IsCubeMap() ? D3D11_RESOURCE_MISC_TEXTURECUBE : 0);
CD3D11_SHADER_RESOURCE_VIEW_DESC srvDesc(hdr->IsCubeMap() ? D3D_SRV_DIMENSION_TEXTURECUBE : D3D_SRV_DIMENSION_TEXTURE2D, desc.Format, 0, -1);
std::vector<D3D11_SUBRESOURCE_DATA> r;
int offset = 128;
for (int a = 0; a < arraySize; a++) {
for (int m = 0; m < mipCnt; m++) {
int w = std::max(1, hdr->w >> m);
int h = std::max(1, hdr->h >> m);
r.push_back({ (char*)img + offset, (uint32_t)pitchCalcurator(w, 1), 0 });
offset += pitchCalcurator(w, h);
}
}
ComPtr<ID3D11Texture2D> tex;
deviceMan11.GetDevice()->CreateTexture2D(&desc, &r[0], &tex);
deviceMan11.GetDevice()->CreateShaderResourceView(tex.Get(), &srvDesc, &srv);
free(img);
return srv;
}
view raw dds_loader.cpp hosted with ❤ by GitHub


ID3D11Texture2DとDDSファイルは共にキューブマップやミップマップの複数のイメージを内包します。よって、DDSからそれらを含むID3D11Texture2Dを生成するときにD3D11_SUBRESOURCE_DATAの配列でその複数イメージを指定します。

ミップマップ入りキューブマップ等になるとD3D11_SUBRESOURCE_DATAの順で悩みそうですが、これはIntroduction To Textures in Direct3D 11が詳しいです。更に都合のよいことに、D3D11_SUBRESOURCE_DATAとDDSの画像の配列順は同じになっているようです。

また、各画像の開始位置を正確にD3D11_SUBRESOURCE_DATAに設定しなければいけませんが、テクスチャ用の DDS ファイルのレイアウトと題されたページが参考になります。

やってみると意外と短いコードで出来たので紹介してみました。

No comments:

Post a Comment