Reading one texel from depthbuffer

Started by
12 comments, last by Adam_42 7 years, 2 months ago

I was trying to find something similiar to glReadPixel() and stumbled upon this post: https://www.gamedev.net/topic/594722-reading-one-pixel-from-texture-to-cpu-in-dx11/

Seemed reasonable, tried it but lo and behold didnt work.

Sample code;


{
	DX11DepthBufferReadback::DX11DepthBufferReadback(ID3D11DevicePtr device, ID3D11DeviceContextPtr context, ID3D11Texture2DPtr depthBuffer) :
		mContext(context),
		mDepthbuffer(depthBuffer),
		mStaging1x1(nullptr)
	{
		D3D11_TEXTURE2D_DESC depthTextureDesc;
		ZeroMemory(&depthTextureDesc, sizeof(depthTextureDesc));
		depthBuffer->GetDesc(&depthTextureDesc);

		depthTextureDesc.Usage = D3D11_USAGE_STAGING;
		depthTextureDesc.BindFlags = 0;
		depthTextureDesc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
		device->CreateTexture2D(&depthTextureDesc, NULL, &mStaging1x1);
	}


	float DX11DepthBufferReadback::GetDepthValue(const WindowPosition& position) const
	{
		D3D11_BOX box;
		ZeroMemory(&box, sizeof(box));
		box.left = position.x;
		box.right = box.left + 1;
		box.top = position.y;
		box.bottom = box.top + 1;
		box.front = 0;
		box.back = 1;

		mContext->CopySubresourceRegion(mStaging1x1, 0, 0, 0, 0, mDepthbuffer, 0, &box);

		D3D11_MAPPED_SUBRESOURCE mappedRes;
		mContext->Map(mStaging1x1, 0, D3D11_MAP_READ, 0, &mappedRes);
		float depth = *static_cast<float*>(mappedRes.pData);
		mContext->Unmap(mStaging1x1, 0);

		return depth;
	}
}

This is the debug ouput I get:


D3D11 ERROR: ID3D11DeviceContext::CopySubresourceRegion: If CopySubresourceRegion is used with Multisampled or D3D11_BIND_DEPTH_STENCIL Resources, then the whole Subresource must be copied. DstX, DstY, and DstZ must all be 0, while pSrcBox must be NULL. [ RESOURCE_MANIPULATION ERROR #280: COPYSUBRESOURCEREGION_INVALIDSOURCEBOX]

So this dosn't seem to work. Is there any similiarly easy/straight forward solution that works?

Advertisement

You could write a single-thread, single-wave Compute Shader that reads a single sample from the texel you're interested in and writes that to a USAGE_DEFAULT buffer. You can then issue a CopySubresourceRegion to copy the single float from a USAGE_DEFAULT buffer to a USAGE_STAGING buffer and map that for readback instead. You can't call ResourceSubresource on an MSAA depth buffer, so you'll need to use a shader (CS or PS) to extract the data you're interested in and write that to another resource. If you'd like to avoid Compute Shaders you could write the extracted depth value to a 1x1 R32_FLOAT render target and copy that texture instead to a STAGING resource.

Adam Miles - Principal Software Development Engineer - Microsoft Xbox Advanced Technology Group

You could write a single-thread, single-wave Compute Shader that reads a single sample from the texel you're interested in and writes that to a USAGE_DEFAULT buffer. You can then issue a CopySubresourceRegion to copy the single float from a USAGE_DEFAULT buffer to a USAGE_STAGING buffer and map that for readback instead. You can't call ResourceSubresource on an MSAA depth buffer, so you'll need to use a shader (CS or PS) to extract the data you're interested in and write that to another resource. If you'd like to avoid Compute Shaders you could write the extracted depth value to a 1x1 R32_FLOAT render target and copy that texture instead to a STAGING resource.

This is my depthbuffer, also used for my staging texture except for cpuAccess, usage and bindflags:


// create depth buffer
D3D11_TEXTURE2D_DESC depthStencilBufferDesc;
ZeroMemory(&depthStencilBufferDesc, sizeof(D3D11_TEXTURE2D_DESC));
depthStencilBufferDesc.ArraySize = 1;
depthStencilBufferDesc.BindFlags = D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_DEPTH_STENCIL;
depthStencilBufferDesc.Format = DXGI_FORMAT_R24G8_TYPELESS;
depthStencilBufferDesc.Width = backbufferTextureDesc.Width;
depthStencilBufferDesc.Height = backbufferTextureDesc.Height;
depthStencilBufferDesc.MipLevels = 1;
depthStencilBufferDesc.SampleDesc.Count = 1;
depthStencilBufferDesc.Usage = D3D11_USAGE_DEFAULT;

depthStencilBufferDesc.SampleDesc.Count = 1 is not multisampled right? (i.e it should work?)

Correct, but the error message also says you can't copy just a sub-region of the depth buffer, you have to copy it at all. You appear to be trying to copy a 1x1 region.

You can either copy the entire thing back to system memory and just read the texel you're interested in, or you can extract the relevant texel on the GPU and just copy that back.

I'm not sure whether you might encounter difficulty copying back D24S8 too, I've never tried that. Given that some hardware doesn't pack D24 and S8 together into a single DWORD I don't know what the memory layout of the system memory copy of this surface would look like. It wouldn't surprise me if you encounter further validation errors.

Adam Miles - Principal Software Development Engineer - Microsoft Xbox Advanced Technology Group

Correct, but the error message also says you can't copy just a sub-region of the depth buffer, you have to copy it at all. You appear to be trying to copy a 1x1 region.

You can either copy the entire thing back to system memory and just read the texel you're interested in, or you can extract the relevant texel on the GPU and just copy that back.

I'm not sure whether you might encounter difficulty copying back D24S8 too, I've never tried that. Given that some hardware doesn't pack D24 and S8 together into a single DWORD I don't know what the memory layout of the system memory copy of this surface would look like. It wouldn't surprise me if you encounter further validation errors.

What do you estimate would be the most efficient way of doing it (copy entire depthbuffer and read one texel -VS- use a CS/PS to output to a 1x1 staging texture and read that?)

There's only limited bandwidth back across the PCI-E bus, so ideally you'd be copying as little data as possible over that (which favours the CS/PS approach). A 1080p depth buffer is at least 8MB, heading up to 32MB by the time your resolution is 4K. With the single texel extraction you can copy back a fixed amount (4 bytes) irrespective of your resolution.

Adam Miles - Principal Software Development Engineer - Microsoft Xbox Advanced Technology Group

I would suggest don't do it at all. Aside from bandwidth, even a single pixel readback will cause the GPU and CPU to synchronize, potentially cutting your performance by up to two-thirds. Think about what you want to do rather than how you want to do it - is there an alternative approach that doesn't involve a readback?

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

Copying back the texel on a one frame delay shouldn't do much to impact the frame rate. So long as there's latency tolerance built into the system there's nothing particularly harmful about a small readback.

Adam Miles - Principal Software Development Engineer - Microsoft Xbox Advanced Technology Group

I am doing this in my editor to raise/lower terrain (i.e increment/decrement values in a heightmap). To do that I first need to reconstruct the 3d point from 2d screen coords and for that I need depth for that pixel (afaik) and then somehow go from that 3d point to modify the correct hieghtmap texel (but that is a later problem).

... If CopySubresourceRegion is used with Multisampled or D3D11_BIND_DEPTH_STENCIL Resources ...

Do you have a need for a stencil buffer? If not, try changing DXGI_FORMAT_R24G8_TYPELESS to DXGI_FORMAT_R32_TYPELESS (and no D3D11_BIND_DEPTH_STENCIL) and see if it works..?

.:vinterberg:.

This topic is closed to new replies.

Advertisement