c++directx-11directx-9texture2dmipmaps

difference between dx9 and dx11 mipmaps


I had tried to generate mipmaps for both DX9 and DX11 in same way, i needed them to sample specific levels from pixel shader. After that i noticed that sampling is different on different DX versions, and seems to be worse with DX11 and good with DX9.

Next i tried to visualize it by rendering a backbuffer copy image and 4x downscaled size with ImGui and the results were:

DX9 with mipmaps generation: dx9 w mips

DX9 without mipmaps generation: dx9 wo mips

DX11 with mipmaps generation: dx11 w mips

DX11 without mipmaps generation: dx11 wo mips

The texture creation is absolutely same:

DX9

static IDirect3DTexture9* pBackBufferResource = nullptr;

IDirect3DSurface9* pBackBuffer = nullptr;
g_pd3dDevice->GetBackBuffer(0U, 0U, D3DBACKBUFFER_TYPE_MONO, &pBackBuffer);

D3DSURFACE_DESC descBack = { };
pBackBuffer->GetDesc(&descBack);

if (pBackBufferResource == nullptr)
{
    g_pd3dDevice->CreateTexture(descBack.Width, descBack.Height, 1U, D3DUSAGE_RENDERTARGET | D3DUSAGE_AUTOGENMIPMAP, descBack.Format, D3DPOOL_DEFAULT, &pBackBufferResource, nullptr);

    /*
     * explicitly set linear filter for mipmap generation
     * for some reason 'D3DSAMP_MIPFILTER' sampler state doesn't actually change this
     */
    pBackBufferResource->SetAutoGenFilterType(D3DTEXF_LINEAR);
}

// get the most detailed mip-level surface
IDirect3DSurface9* pResourceTopSurface = nullptr;
pBackBufferResource->GetSurfaceLevel(0U, &pResourceTopSurface);

// because backbuffer haven't mipmap, we couldn't just copy its texture, we're update only most detailed mip-level
g_pd3dDevice->StretchRect(pBackBuffer, nullptr, pResourceTopSurface, nullptr, D3DTEXF_LINEAR);

// the 'D3DUSAGE_AUTOGENMIPMAP' flag of texture creation will regenerate mipmap automatically, but here do it manually just for sure
pBackBufferResource->GenerateMipSubLevels();

ImVec2 vecMin = { 10.f, 10.f };
ImVec2 vecMax = { vecMin.x + floorf((float)descBack.Width / 4.0f), vecMin.y + floorf((float)descBack.Height / 4.0f) };
ImGui::GetForegroundDrawList()->AddImage(pBackBufferResource, vecMin, vecMax);
ImGui::GetForegroundDrawList()->AddRect(vecMin, vecMax, IM_COL32(0, 0, 0, 255));

DX11

static ID3D11Texture2D* pBackBufferCopy = nullptr;
static ID3D11ShaderResourceView* pBackBufferResource = nullptr;

ID3D11Texture2D* pBackBuffer = nullptr;
g_mainRenderTargetView->GetResource(reinterpret_cast<ID3D11Resource**>(&pBackBuffer));

D3D11_TEXTURE2D_DESC descBack = { };
pBackBuffer->GetDesc(&descBack);

if (pBackBufferCopy == nullptr)
{
    D3D11_TEXTURE2D_DESC descBackCopy = descBack;
    descBackCopy.BindFlags = D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_RENDER_TARGET;
    descBackCopy.MipLevels = 0U;
    descBackCopy.MiscFlags = D3D11_RESOURCE_MISC_GENERATE_MIPS;
    g_pd3dDevice->CreateTexture2D(&descBackCopy, nullptr, &pBackBufferCopy);
}

// because backbuffer haven't mipmap, we couldn't copy its texture just with 'CopyResource()', so we're update only most detailed mip-level
g_pd3dDeviceContext->CopySubresourceRegion(pBackBufferCopy, 0U, 0U, 0U, 0U, pBackBuffer, 0U, nullptr);

pBackBuffer->Release();

if (pBackBufferResource == nullptr)
{
    D3D11_SHADER_RESOURCE_VIEW_DESC resourceViewDescBuffer = { };
    resourceViewDescBuffer.Format = descBack.Format;
    resourceViewDescBuffer.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D;
    resourceViewDescBuffer.Texture2D.MostDetailedMip = 0U;
    resourceViewDescBuffer.Texture2D.MipLevels = ~0U;
    g_pd3dDevice->CreateShaderResourceView(pBackBufferCopy, &resourceViewDescBuffer, &pBackBufferResource);
}

// regenerate mipmap based on updated most detailed mip-level
g_pd3dDeviceContext->GenerateMips(pBackBufferResource);

ImVec2 vecMin = { 10.f, 10.f };
ImVec2 vecMax = { vecMin.x + floorf((float)descBack.Width / 4.0f), vecMin.y + floorf((float)descBack.Height / 4.0f) };
ImGui::GetForegroundDrawList()->AddImage(pBackBufferResource, vecMin, vecMax);
ImGui::GetForegroundDrawList()->AddRect(vecMin, vecMax, IM_COL32(0, 0, 0, 255));

Sampler states of MIN/MAG/MIP filters are LINEAR and MaxLOD is D3D11_FLOAT32_MAX for both renderers. It's clear that DX11 image are excessively sharp and looking same w/ and w/o mipmaps. So my question is why it happens with DX11 and doesn't happens with DX9, when i'm sure that it does generate mipmaps properly and i can sample from them inside shader (shader sampling is also looking sharper)?


Solution

  • The general answer is that auto-generated mipmips is left entirely up to the driver. As such, the quality of auto-generated mips is going vary widely from driver to driver, and in some cases for some formats it can be as low-quality as point-sampling.

    This is in fact one of the reasons that DirectX 12 doesn't have 'auto-generated mips' as a feature. Instead if you need this feature, the recommendation is to use a compute shader. For an example, see DirectX Tool Kit for DX12.

    All that said, you need to validate all functions that return HRESULT values as you are likely getting an error code back somewhere you are ignoring. For example, I doubt that calling SetAutoGenFilterType on a swapchain buffer actually does what you think it does. Even if you just treat them as fail fast, don't ignore HRESULT returns.

    For DirectX 11, you should enable the debug device and look for output. Since DirectX 9 is legacy, the 'debug device' for it has not been supported since Windows 7.