Copy texture from back buffer in Directx12

Started by
8 comments, last by Fresa 7 years, 9 months ago

I am trying to read a texture from the back buffer of a swap chain in DirectX12 using SharpDX, but I can't figure it out. I get an "The parameter is incorrect" exception when trying to map the texture resource, and I can't get anymore debug info than that.

The code:


TextureCopyLocation destLocation;
TextureCopyLocation srcLocation;

using (var backBuffer = swapChain.GetBackBuffer<Resource>(0))
{
    var display = new Display
    {
        Height = backBuffer.Description.Height,
        Width = (int)backBuffer.Description.Width
    };

    var description = swapChain.Description;
    var description1 = new SwapChainDescription1
    {
        BufferCount = description.BufferCount,
        Flags = description.Flags,
        SampleDescription = description.SampleDescription,
        SwapEffect = description.SwapEffect,
        Usage = description.Usage,
        Format = description.ModeDescription.Format,
        Width = description.ModeDescription.Width,
        Height = description.ModeDescription.Height,
        AlphaMode = AlphaMode.Ignore,
        Scaling = Scaling.None,
        Stereo = false
    };
}
SwapChain1 swapChain;

using (var factory = new Factory4())
{
    _device = new Device(null, FeatureLevel.Level_12_0);
    _commandQueue = device.CreateCommandQueue(new CommandQueueDescription(CommandListType.Direct));

    using (var form = new RenderForm())
    {
        swapChain = new SwapChain1(factory, queue, form.Handle, ref description1);
    }
    destLocation = new TextureCopyLocation(_screenTexture, 0);
    srcLocation = new TextureCopyLocation(backBuffer, 0);
}
var textureDesc = ResourceDescription.Texture2D(Format.R8G8B8A8_UNorm, display.Width, display.Height);
_screenTexture = _device.CreateCommittedResource(new HeapProperties(HeapType.Default), HeapFlags.None, textureDesc, ResourceStates.CopyDestination);

var commandAllocator = _device.CreateCommandAllocator(CommandListType.Direct);
var commandList = _device.CreateCommandList(CommandListType.Direct, commandAllocator, null);

commandList.ResourceBarrierTransition(_screenTexture, ResourceStates.GenericRead, ResourceStates.CopyDestination);
commandList.CopyTextureRegion(destLocation, 0, 0, 0, srcLocation, null);
commandList.ResourceBarrierTransition(_screenTexture, ResourceStates.CopyDestination, ResourceStates.GenericRead);

commandList.Close();
_commandQueue.ExecuteCommandList(commandList);

_fence = _device.CreateFence(0, FenceFlags.None);
_fenceValue = 1;
_fenceEvent = new AutoResetEvent(false);

int localFence = _fenceValue;
_commandQueue.Signal(this._fence, localFence);
_fenceValue++;

// Wait until the previous frame is finished.
if (_fence.CompletedValue < localFence)
{
    _fence.SetEventOnCompletion(localFence, _fenceEvent.SafeWaitHandle.DangerousGetHandle());
    _fenceEvent.WaitOne();
}
var pnt = _screenTexture.Map(0);
var dataRectangle = new DataRectangle { DataPointer = pnt, Pitch = (int)_screenTexture.Description.Width * 4 };
var dataStream = new DataStream(new DataPointer(pnt, _screenTexture.Description.DepthOrArraySize));
Advertisement

_screenTexture needs to be a HeapType.Readback buffer. Then you should use _device.GetCopyableFootprints to fill out the destLocation.

This is coming from C++ API usage, but SharpDX looks close enough.

_screenTexture needs to be a HeapType.Readback buffer. Then you should use _device.GetCopyableFootprints to fill out the destLocation.

This is coming from C++ API usage, but SharpDX looks close enough.

Thanks for your reply! I actually tested to create a readback heap before, but that just throws an "The parameter is incorrect" exception (seriously, isn't there any way to get more information on what parameter is expected?)


_screenTexture = _device.CreateCommittedResource(
    new HeapProperties(HeapType.Readback)
    {
        CPUPageProperty = CpuPageProperty.Unknown,
        MemoryPoolPreference = MemoryPool.Unknown,
        CreationNodeMask = 1,
        VisibleNodeMask = 1
    }, 
    HeapFlags.None, 
    textureDesc, 
    ResourceStates.CopyDestination,
    null);

About the GetCopyableFootprints, I guess I would do that after the map? Like this:


var pnt = _screenTexture.Map(0);

var resourceDesc = _screenTexture.Description;
long totalBytes;
_device.GetCopyableFootprints(ref resourceDesc, 0, 1, 0, null, null, null, out totalBytes);

var dataRectangle = new DataRectangle
{
    DataPointer = pnt,
    Pitch = (int)_screenTexture.Description.Width * 4
};
var dataStream = new DataStream(new DataPointer(pnt, (int)totalBytes));

Thanks for your reply! I actually tested to create a readback heap before, but that just throws an "[background=#fafbfc]The parameter is incorrect" exception (seriously, isn't there any way to get more information on what parameter is expected?)[/background]

Yep - enable the debug layer before your create your device (C++ code, sorry):


	ID3D12Debug* debugController=0;
	if(SUCCEEDED(D3D12GetDebugInterface(IID_PPV_ARGS(&debugController))))
	{
		releaseOnError.Add(debugController);
		debugController->EnableDebugLayer();
	}

FWIW, is there a particular reason you're trying to read the backbuffer instead of a different render-target? In my experience in writing portable code, it's often best to assume that the backbuffer is special and write-only :)


Thanks for your reply! I actually tested to create a readback heap before, but that just throws an "The parameter is incorrect" exception (seriously, isn't there any way to get more information on what parameter is expected?)

Yep - enable the debug layer before your create your device (C++ code, sorry):


	ID3D12Debug* debugController=0;
	if(SUCCEEDED(D3D12GetDebugInterface(IID_PPV_ARGS(&debugController))))
	{
		releaseOnError.Add(debugController);
		debugController->EnableDebugLayer();
	}

FWIW, is there a particular reason you're trying to read the backbuffer instead of a different render-target? In my experience in writing portable code, it's often best to assume that the backbuffer is special and write-only :)

Ah, yeah, I've tried that, it just crashes my host application.

I'm building a kind of screen capture application. It injects a small client into a running application where it hooks into the applications directx pipeline. So for example, I can listen to when the application sends Present to the GPU and process that frame using the provided swapchain before it hits the GPU.

If it's crashing, are you just not checking for success/failure? If it's failing, make sure you've got the directx debug layer installed.

If it's crashing, are you just not checking for success/failure? If it's failing, make sure you've got the directx debug layer installed.

Graphics Tools are installed (I'm running Windows 10). In SharpDX you use


DebugInterface.Get().EnableDebugLayer();

to enable debugging. It basically does this:


IntPtr debugInterfacePtr;
D3D12.GetDebugInterface(Utilities.GetGuidFromType(typeof(DebugInterface)), out debugInterfacePtr);
return new DebugInterface(debugInterfacePtr);

However that crashes the app when trying to create a device.

HRESULT: [0x887A0007], Module: [SharpDX.DXGI], ApiCode: [DXGI_ERROR_DEVICE_RESET/DeviceReset], Message: The GPU will not respond to more commands, most likely because some other application submitted invalid commands.
The calling application should re-create the device and continue.
, at SharpDX.Result.CheckError()
at SharpDX.Direct3D12.Device.CreateDevice(Adapter adapter, FeatureLevel minFeatureLevel, Device instance)
at SharpDX.Direct3D12.Device..ctor(Adapter adapter, FeatureLevel minFeatureLevel)
It does however work when not executing in another host application, so maybe that application is preventing debugging somehow.

Okey, after fiddling a bit with the debug layer and creating the device locally, I got some debug info. When executing CreateCommittedResource I get:

D3D12 ERROR: ID3D12Device::CreateCommittedResource: A texture resource cannot be created on a D3D12_HEAP_TYPE_UPLOAD or D3D12_HEAP_TYPE_READBACK heap. Investigate CopyTextureRegion to copy texture data in CPU accessible buffers, or investigate D3D12_HEAP_TYPE_CUSTOM and WriteToSubresource for UMA adapter optimizations. [ STATE_CREATION ERROR #638: CREATERESOURCEANDHEAP_INVALIDHEAPPROPERTIES]

Question: How do I create a texture associated with a ReadBack heap?
Code:

_device = new Device(null, FeatureLevel.Level_12_0);
var textureDesc = ResourceDescription.Texture2D(Format.R8G8B8A8_UNorm,
	100,
	100, 1, 0, 1, 0, ResourceFlags.AllowRenderTarget, TextureLayout.Unknown);

var _screenTexture = _device.CreateCommittedResource(
	new HeapProperties(HeapType.Readback)
	{
		CPUPageProperty = CpuPageProperty.Unknown,
		MemoryPoolPreference = MemoryPool.Unknown,
		CreationNodeMask = 1,
		VisibleNodeMask = 1
	},
	HeapFlags.None,
	textureDesc,
	ResourceStates.CopyDestination,
	null);

_screenTexture needs to be a HeapType.Readback buffer. Then you should use _device.GetCopyableFootprints to fill out the destLocation.

This is coming from C++ API usage, but SharpDX looks close enough.

So, creating a texture implies that the GPU needs to know that it is a texture. D3D12 doesn't allow creation of CPU-accessible textures by default, unless you use a CUSTOM heap type. What D3D12 does allow you to do is copy from texture to buffer, using CopyTextureRegion. So the intended mechanism is to create a readback buffer and copy to it. You specify the desired layout of the buffer during the copy - for a default, valid layout, you can use GetCopyableFootprints to produce the destination layout desc.

As for the debug layer issue, the debug layer needs to be enabled before creating a device. Doing so afterwards will cause all previously existing devices to be removed.

_screenTexture needs to be a HeapType.Readback buffer. Then you should use _device.GetCopyableFootprints to fill out the destLocation.

This is coming from C++ API usage, but SharpDX looks close enough.

So, creating a texture implies that the GPU needs to know that it is a texture. D3D12 doesn't allow creation of CPU-accessible textures by default, unless you use a CUSTOM heap type. What D3D12 does allow you to do is copy from texture to buffer, using CopyTextureRegion. So the intended mechanism is to create a readback buffer and copy to it. You specify the desired layout of the buffer during the copy - for a default, valid layout, you can use GetCopyableFootprints to produce the destination layout desc.

As for the debug layer issue, the debug layer needs to be enabled before creating a device. Doing so afterwards will cause all previously existing devices to be removed.

That explains why I cannot enable debugging when inside another directx application; the device has already been created when my application have been injected. Makes sense.

Regarding reading back the 2DTexture resource to a buffer heap, I actually tried that a couple of days ago, but never got it working. But I gave it another chance, and I actually got it to work, so thank you very much for the directions, much appreciated! :)

I can now read the pixel data from the 2DTexture using the following code. Please have a look at it and give me some feedback if there is something I should consider changing. Thanks :)


using (var backBuffer = swapChain.GetBackBuffer<Resource>(0))
    {
        var device = new Device(null, FeatureLevel.Level_12_0);
        _commandQueue = device.CreateCommandQueue(new CommandQueueDescription(CommandListType.Direct));

        PlacedSubResourceFootprint[] footprints = { new PlacedSubResourceFootprint() };
        long bufftotalBytes;
        var description = backBuffer.Description;
        device.GetCopyableFootprints(ref description, 0, 1, 0, footprints, null, null, out bufftotalBytes);

        var readBackBufferDescription = ResourceDescription.Buffer(new ResourceAllocationInformation
        {
            Alignment = 0,
            SizeInBytes = bufftotalBytes
        });

        var readBackBuffer = device.CreateCommittedResource(
            new HeapProperties(HeapType.Readback)
            {
                CPUPageProperty = CpuPageProperty.Unknown,
                MemoryPoolPreference = MemoryPool.Unknown,
                CreationNodeMask = 1,
                VisibleNodeMask = 1
            },
            HeapFlags.None,
            readBackBufferDescription,
            ResourceStates.CopyDestination,
            null);

        var srcLocation = new TextureCopyLocation(backBuffer, 0);
        var destLocation = new TextureCopyLocation(readBackBuffer, new PlacedSubResourceFootprint
        {
            Footprint = footprints[0].Footprint
        });

        var commandAllocator = device.CreateCommandAllocator(CommandListType.Direct);
        var commandList = device.CreateCommandList(CommandListType.Direct, commandAllocator, null);

        commandList.CopyTextureRegion(destLocation, 0, 0, 0, srcLocation, null);

        commandList.Close();
        _commandQueue.ExecuteCommandList(commandList);

        _fence = device.CreateFence(0, FenceFlags.None);
        _fenceValue = 1;
        _fenceEvent = new AutoResetEvent(false);

        WaitForPreviousFrame();

        var pnt = readBackBuffer.Map(0);

        var dataRectangle = new DataRectangle
        {
            DataPointer = pnt,
            Pitch = footprints[0].Footprint.RowPitch
        };
        var dataStream = new DataStream(new DataPointer(pnt, (int)bufftotalBytes));

        /* Read pixels from datastream using dataRectangle */
                
        readBackBuffer.Unmap(0);

        device.Dispose();
        readBackBuffer.Dispose();
        _commandQueue.Dispose();
    }

    _fence.Dispose();
}

This topic is closed to new replies.

Advertisement