Jump to content
  • Advertisement
Sign in to follow this  
Fresa

Copy texture from back buffer in Directx12

This topic is 749 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

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));

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites

_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));
Edited by Fresa

Share this post


Link to post
Share on other sites

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 :)

Share this post


Link to post
Share on other sites

 


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.

Share this post


Link to post
Share on other sites

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.
Edited by Fresa

Share this post


Link to post
Share on other sites

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);

Share this post


Link to post
Share on other sites

_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.

Share this post


Link to post
Share on other sites

 

_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();
}
Edited by Fresa

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!