Direct3D9 Depth Buffer access

If you have to deal with Depth Buffer reading in Direct3D9, you’ll propably encounter the documentation saying that you cannot read the GPU depth buffer directly. But there’s a workaround with special buffer formats on NVIDIA Cards (RAWZ on older cards and INTZ on G80+ series) and on ATI (DF16/24, I think). There’s also a paragraph in the G80 documentation by NVIDIA (see http://developer.nvidia.com/object/gpu_programming_guide.html). I think GTA4 is a game that uses the formats. Here’s some code I have been playing around with using Microsofts DirectX SDK of November 2008:

// decls

IDirect3DTexture9* m_pSMZTexture;   // rawz for using dss as input for next shader
LPDIRECT3DSURFACE9 m_pBackBuffer;   // default

// creates NVIDIA INTZ Format DSS. Use as DSS and render scene. Then use as input texture for another pass.
BOOL vmD3D::SetupDepthBufferAccess()
{
if(!m_pd3dDevice)
return FALSE;

if(FAILED(m_pd3dDevice->CreateTexture(TEXDEPTH_WIDTH, TEXDEPTH_HEIGHT, 1,
D3DUSAGE_DEPTHSTENCIL, (D3DFORMAT)MAKEFOURCC('I','N','T','Z'),
D3DPOOL_DEFAULT, &m_pSMZTexture, NULL)))
{
return FALSE;
}

return TRUE;
}

This actually creates an INTZ-Format DSS. Be sure to use the same dimensions of the render target you’ll bind on the same drawcall. Next, before your dracall bind the dss:

// setdss (our custom zsurf) -> intz!
// first fetch surface from tex at level 0
IDirect3DSurface9 *pSMZSurf = NULL;
m_pSMZTexture->GetSurfaceLevel(0, &pSMZSurf);
// set dss:
if(FAILED(m_pd3dDevice->SetDepthStencilSurface(pSMZSurf)))
return FALSE;
pSMZSurf->Release();

When drawing, there’s no need for any shader stuff if you simply want to output depth-only values (just write black color for example). But obviosly your vertex shader should do the needed transformations (multiply your WorldProjectionMatrix for example). After drawing with the intz-dss bound, you can use the texture as shader input for a next render pass. Here you can simply render a Full-Screen Quad to visualize the depth values:

if(FAILED(pFxDisplayDepth->SetTexture("DepthMap", m_pSMZTexture)))
return FALSE;

//set render target back to normal back buffer / depth buffer (or disable Z - as it's a dumb quad!)
if(FAILED(m_pd3dDevice->SetRenderTarget(0, m_pBackBuffer)))
return FALSE;

Then just draw the quad, i.e. with lazy UP RHW drawing:

UINT uPasses;
if (SUCCEEDED(pFxDisplayDepth->Begin(&uPasses, 0)))
{
for (UINT uPass = 0; uPass BeginPass(uPass);
m_pd3dDevice->SetFVF( m_pQuad->FVF );
m_pd3dDevice->DrawPrimitiveUP( D3DPT_TRIANGLESTRIP, 2, m_pQuad->m_Vertex, sizeof( FullScreenQuad::Vertex ) );
pFxDisplayDepth->EndPass();
}
pFxDisplayDepth->End();
}

Be aware that Microsoft PIX cannot handle the INTZ resource, so you won’t be able to see a preview or the metadata of the INTZ-Surface.