Generate mipmaps for DDS cubemaps in DirectX 11

Started by
6 comments, last by MJP 9 years, 6 months ago

I'm loading my DDS cubemaps with this function:

HRESULT hr = CreateDDSTextureFromFile(mDevice, inPath, &Texture, &mShaderResourceView);

This works fine, except it has no mipmaps.

According to this page:

https://directxtk.codeplex.com/wikipage?title=DDSTextureLoader

I have to use the Ex version:

unsigned int bindFlags = D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_RENDER_TARGET;
unsigned int miscFlags = D3D11_RESOURCE_MISC_GENERATE_MIPS;

HRESULT hr=CreateDDSTextureFromFileEx(mDevice, mContext, inPath, 0, D3D11_USAGE_DEFAULT, bindFlags, 0, miscFlags, false, &Texture, &mShaderResourceView);

However, no matter what I try I get a:

E_INVALIDARG One or more arguments are invalid

When stepping through the code I found out that the format is DXGI_FORMAT_BC1_UNORM (71) and this format is not supported for auto-gen mipmaps for my feature level and it finally fails at the CreateD3DResources function.

DirectX 9 has no problems generating mipmaps for this very same DDS file. Does this mean I have to convert the DDS file to another format, like uncompressed? Or is there a way to make it work in DirectX 11?

Any hints will be much appreciated!

Advertisement

Why not just include the mipmap levels in the DDS file itself? That way you don't need to generate them.

Direct3D has need of instancing, but we do not. We have plenty of glVertexAttrib calls.

I understand that this is the idea of DDS files.

We however need this functionality to stay compatible with the DirectX 9 version of our software.

Our users are expecting their projects (with their own DDS files) to render better in a DirectX 11 version. Not worse.

For what I can see it is failing because what DX9 did was to decompress, generate mips, compress again. Which is a lossy operation (theoretically it can be done as a lossless conversion by replacing binary data of mip 0 of the recompressed stream with the one from the original bc1, however the generated mips from bc1 sources should be of lower quality than generating mips from original sources).

Most likely D3D11 forces you to greater quality by first generating the mips from the source material, then compress. If the dds is already compressed and you want to pay the price, decompress it first.

You can't use a BC1 texture as a render target, which means that you can't use the GPU to generate mipmaps. Generating mipmaps would require uncompressing the texture data (either on the CPU or GPU), generating the mips, and then optionally re-compressing at the end. The simple DDS loader that you're using doesn't include this functionality, since it's meant to be simple and bare-bones.

One option for you would be to link to the full version of DirectXTex, and use it to perform the necessary conversions and operations. It has a full suite of texture functionality, and can certainly handle the process that I outlined above. If you prefer it also includes a command-line texture conversion utility, which has most of the same functionality that's available in the library.

Another option would be to do the conversion yourself on the GPU. This actually fairly straightforward: just create an equivalent-sized uncompressed version of the texture, and then use a compute shader or a pixel shader on a full-screen quad to copy the texture data to your uncompressed texture. Then you can use the GPU to auto-generate the mips, and just render with the uncompressed version. Or if you would prefer to have the memory savings of the compressed version, you can re-compress at this point using DirectXTex or the compression library of your choice.

Thank you for the info.

I think I'try the latter option first. I'll let you know how it goes.

I managed to add mipmapping to the BC1 encoded DDS cubemaps. Here's what I do:
1) Create a texture using the CreateDDSTextureFromFile function

ID3D11Resource* Texture = nullptr;
 
HRESULT hr = CreateDDSTextureFromFile(mDevice, inPath, &Texture, &mShaderResourceView);
2) Get the resolution

D3D11_TEXTURE2D_DESC d;
ZeroMemory(&d, sizeof(d));
 
Texture->GetDesc(&d);
mWidth = d.Width;
mHeight = d.Height;
3) Create a new cubemap texture with the same resolution

mFormat = DXGI_FORMAT_B8G8R8A8_UNORM;
 
D3D11_TEXTURE2D_DESC d;
ZeroMemory(&d, sizeof(d));
 
d.Width = mWidth;
d.Height = mHeight;
d.MipLevels = 0;
d.ArraySize = 6;
d.Format = mFormat;
d.SampleDesc.Count = 1;
d.SampleDesc.Quality = 0;
d.Usage = D3D11_USAGE_DEFAULT;
d.BindFlags = D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_RENDER_TARGET;
d.CPUAccessFlags = 0;
d.MiscFlags = D3D11_RESOURCE_MISC_TEXTURECUBE | D3D11_RESOURCE_MISC_GENERATE_MIPS;
 
HRESULT hr = mDevice->GetDevice()->CreateTexture2D(&d, nullptr, &mTexture);
 
4) Create a shader resource view
 
D3D11_SHADER_RESOURCE_VIEW_DESC d;
ZeroMemory(&d, sizeof(d));
 
d.Format = mFormat;
d.ViewDimension = D3D11_SRV_DIMENSION_TEXTURECUBE
d.Texture2D.MipLevels = -1;
 
HRESULT hr = mDevice->CreateShaderResourceView(mTexture, &d, &mShaderResourceView);
5) For all 6 faces: create a render target

D3D11_RENDER_TARGET_VIEW_DESC d;
ZeroMemory(&d, sizeof(d));
 
d.Format = mFormat;
d.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2DARRAY;
d.Texture2DArray.ArraySize = 1;
d.Texture2DArray.FirstArraySlice = inFace;
 
HRESULT hr = mDevice->CreateRenderTargetView(mTexture, &d, &mRenderTargetView);
6) For all 6 faces: select this render target:

mContext->OMSetRenderTargets(1, &mRenderTargetView, nullptr);
7) For all 6 faces, I use my framework to render a square on this render target using the ShaderResourceView created in step 1
When your square mesh has UV coordinates ranging from (0,0) top left to (1,1) bottom right, the pixel shader looks something like this:

float4 PS(VS_OUTPUT input): SV_Target{
 
    float4 c=float4(1,0,0,1);
 
    if(TextureResolution[0].x>0){
 
        float2 uv=input.tex;
 
        float3 v=float3(0,0,0);
 
        int f=round(Prop[0].x);
 
        if(f==0){
            //+X
            v.x=1;
            v.y=1-uv.y*2;
            v.z=1-uv.x*2;
        }
        else if(f==1){
            //-X
            v.x=-1;
            v.y=1-uv.y*2;
            v.z=-1+uv.x*2;
        }
        else if(f==2){
            //+Y
            v.x=-1+uv.x*2;
            v.y=1;
            v.z=-1+uv.y*2;
        }
        else if(f==3){
            //-Y
            v.x=-1+uv.x*2;
            v.y=-1;
            v.z=1-uv.y*2;
        }
        else if(f==4){
            //+Z
            v.x=-1+uv.x*2;
            v.y=1-uv.y*2;
            v.z=1;
        }
        else if(f==5){
            //-Z
            v.x=1-uv.x*2;
            v.y=1-uv.y*2;
            v.z=-1;
        }
        c=MyTexture0.Sample(MySampler0,normalize(v));
    }
    return c;
}
8) After rendering all 6 faces, create the mipmaps

mDevice->GenerateMips(mShaderResourceView);
That's it. Thanks to MJP for pointing me to the right direction!

Thank you for sharing your solution! I'm sure that somebody else will find it useful.

I hope you don't mind, but I edited your post to add "code" tags in order to give the code better formatting.

This topic is closed to new replies.

Advertisement