UPDATE: this issue turned out to be a silly typo (see answer), which was clearly identified by the DirectX debug layer after I was able to enable it. However, I'm leaving the question up as I think the SaveContentsToImage
code might be a useful reference for others wishing to export a multisampled texture.
I'm trying to save the contents of a multisampled texture (from the swapchain) to an image. I'm using Direct3D with feature level 11.0 via SharpDX. I have code (based on this answer and this answer) that works fine when the swapchain is initialized with the SampleDescription count set to 1 (no multisampling), but setting the count to 2, 4, etc. to enable MSAA results in a blank image.
My save-to-image function uses ResolveSubresource to resolve the multisampled texture from the swapchain into a non-multisampled intermediate texture, then uses CopyResource to copy the intermediate texture into a staging texture with CPU read access enabled. Here's the code:
private void SaveContentsToImage()
{
// TODO breaks when using MSAA.
D3D11.Texture2D backBufferTexture = _swapChain.GetBackBuffer<D3D11.Texture2D>(0);
// Intermediate texture used to resolve source using MSAA (unnecessary if source sample count is 1).
D3D11.Texture2DDescription intermediateTextureDesc = backBufferTexture.Description;
intermediateTextureDesc.SampleDescription = new SampleDescription(1, 0);
intermediateTextureDesc.Usage = D3D11.ResourceUsage.Default;
D3D11.Texture2D intermediateTexture = new D3D11.Texture2D(_d3dDevice, intermediateTextureDesc);
_d3DDeviceContext.ResolveSubresource(backBufferTexture, 0, intermediateTexture, 0, backBufferTexture.Description.Format);
//_d3DDeviceContext.ResolveSubresource(_renderTargetView.Resource, 0, intermediateTexture, 0, backBufferTexture.Description.Format); // Works identically to above.
D3D11.Texture2DDescription copyDesc = backBufferTexture.Description;
copyDesc.SampleDescription = new SampleDescription(1, 0);
copyDesc.Usage = D3D11.ResourceUsage.Staging;
copyDesc.BindFlags = D3D11.BindFlags.None;
copyDesc.CpuAccessFlags = D3D11.CpuAccessFlags.Read;
D3D11.Texture2D copyTexture = new D3D11.Texture2D(_d3dDevice, copyDesc);
_d3DDeviceContext.CopyResource(backBufferTexture, copyTexture);
DataStream dataStream;
var dataBox = _d3DDeviceContext.MapSubresource(copyTexture, 0, 0, D3D11.MapMode.Read, D3D11.MapFlags.None, out dataStream);
DataRectangle dataRectangle = new DataRectangle
{
DataPointer = dataStream.DataPointer,
Pitch = dataBox.RowPitch
};
Bitmap wicBitmap = new Bitmap(_wicFactory, copyTexture.Description.Width, copyTexture.Description.Height, PixelFormat.Format32bppBGRA, dataRectangle);
byte[] pixelData = new byte[copyTexture.Description.Width * copyTexture.Description.Height * 4];
wicBitmap.CopyPixels(pixelData, copyTexture.Description.Width * 4);
System.Drawing.Bitmap bitmap = new System.Drawing.Bitmap(copyTexture.Description.Width, copyTexture.Description.Height);
BitmapData bitmapData = bitmap.LockBits(new System.Drawing.Rectangle(0, 0, copyTexture.Description.Width, copyTexture.Description.Height), ImageLockMode.WriteOnly, System.Drawing.Imaging.PixelFormat.Format32bppPArgb);
Marshal.Copy(pixelData, 0, bitmapData.Scan0, pixelData.Length);
bitmap.UnlockBits(bitmapData);
bitmap.Save("test.png");
Console.WriteLine("Saved image.");
}
Here's how the swapchain is configured:
ModeDescription backBufferDescription = new ModeDescription(RenderAreaWidth, RenderAreaHeight, new Rational(60, 1), Format.B8G8R8A8_UNorm);
SwapChainDescription swapChainDescription = new SwapChainDescription()
{
ModeDescription = backBufferDescription,
SampleDescription = new SampleDescription(SampleCount, 0),
Usage = Usage.RenderTargetOutput,
BufferCount = 1,
OutputHandle = _renderForm.Handle,
IsWindowed = true,
};
SampleCount
is an integer constant set to 1, 2, 4, etc. If it's set to 1, SaveContentsToImage()
generates an image that matches what gets rendered to the screen; if it's a higher value, the resulting image is blank.