SpriteBatch in deferred context

Dec 1, 2014 at 7:45 PM
Hi,

I've been using DirectX Tool Kit since a while and find it quite useful for a noobe in DX11 as myself. I used it in immediate context to draw dynamic textures into a target of 3D app. And it worked fine with just begin, draw and end on SharedResourceView by Mpa(), copying buffer and UnMap. Now the problem is I wanted to employ threading as my textures are very dynamic and requires some power to prepared. In order not to do it in the main rander callback 3D app is providing, I wanted to do it in the separeate thread. This is how I run into problems. Using deffered context my textures are not displayed at all. I tried many different paths and options (e.g. deffered sort modes, etc) but none worked. Bascically I create Deferred context and do the same operations Begin, Draw, End and then I call
pDeviceContext->FinishCommandList
and on the main thread and immediate context I call ExecuteCommandList but the textures are not rendered. I tried to combine ExecuteCommandList with Begin and End but it did not work at all.

Any clues what I should do. I spent like hours and still I am not any close to a working solution... According to the docs, SpriteBatch supports deferred context, so most probbaly it's me doing sth wrong ...

Any help appreciated.
Coordinator
Dec 1, 2014 at 7:50 PM
Edited Dec 1, 2014 at 7:51 PM
With deferred contexts, you have to keep the resources active until you actually draw with ExecuteCommandList. You likely need to keep a pool of textures around and then reuse them only once you submit the draw to the immediate context and flush. In that case, there's probably not much value in using Direct3D 11 deferred contexts in the first place.

Direct3D 11 multi-threading is a complex scenario, and unfortunately has a number of limitations in it's current form. See Direct3D 11 Multithreading.
Dec 1, 2014 at 9:14 PM
First I need to know whether Toolkit supports deferred context so I can continue with it.
SIf yes, I need to find what is wrong in my code. It is not so complicated, as I simply update one texture data:

1) Init:

I create do
device->CreateTexture2D( &desc, NULL, &pTexture );
device->CreateShaderResourceView(pTexture, &svDesc, &g_pTexture);
and create deferred context:

mDevice->CreateDeferredContext( 0, &pDeviceContext );

2) in my main thread I do:

MainRender()
{
mDevice->GetImmediateContext( &pDeviceContext );
if(pDeviceContext == NULL)
    return;

// Setup the viewport
D3D11_VIEWPORT vp;
vp.Width = width;
vp.Height = height;
vp.MinDepth = 0.0f;
vp.MaxDepth = 1.0f;
vp.TopLeftX = 0;
vp.TopLeftY = 0;

pDeviceContext->ClearState();
pDeviceContext->RSSetViewports( 1, &vp );

pDeviceContext->OMSetRenderTargets( 1, &pTargetView, NULL );
pDeviceContext->ExecuteCommandList(pd3dCommandList,false);
pDeviceContext->OMSetRenderTargets( 0, NULL, NULL );    
pd3dCommandList->Release();
3) in wy working thread I update the map:

WorkingDraw(int w, int h, float mAlphaLevel )
{
// Setup the viewport
D3D11_VIEWPORT vp;
vp.Width = width;
vp.Height = height;
vp.MinDepth = 0.0f;
vp.MaxDepth = 1.0f;
vp.TopLeftX = 0;
vp.TopLeftY = 0;

pDeviceContext->ClearState();
pDeviceContext->RSSetViewports( 1, &vp );
    D3D11_MAPPED_SUBRESOURCE mappedResource;

    if(pDeviceContext == NULL)
        return;

    m_Sprites->Begin( SpriteSortMode_Immediate);

    HRESULT hr = pDeviceContext->Map(pTexture, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource);

    if (SUCCEEDED(hr))
    {

        BYTE* mappedData = reinterpret_cast<BYTE*>(mappedResource.pData);

        if ( mappedData != NULL)
        {
            for(UINT i = 0; i < h; ++i)
            {
                    memcpy(mappedData, bits,w*4);
                    mappedData += mappedResource.RowPitch;
                    bits += (UINT) w*4;
                }
            }
        }

        pDeviceContext->Unmap(pTexture, 0);

        m_Sprites->Draw( g_pTexture, XMFLOAT2(left, top), nullptr,  Colors::White * mAlphaLevel );

    }

    m_Sprites->End();
    hr = pDeviceContext->FinishCommandList(false, &pd3dCommandList);

}

This does not work. If I put my code (with little changes and without using deffered contect) of WorkingDraw inside MainRender() then my dynamic texture is correctly rendered.

please note, WorkingDraw() is asynchronous to the MainRender() and in different thread.

Does it sounds logic what I tried to do ?
Coordinator
Dec 1, 2014 at 11:10 PM
Edited Dec 1, 2014 at 11:19 PM
I've not done any explicit testing with deferred contexts, but in theory it should work. The driver should alias the resource (see Mapping on Deferred Contexts) with this pattern. That said, I'm not sure it's going to end up being a performance win over just having a pool of texture objects you resuse.

You should create one SpriteBatch instance per context, so m_Sprites here better be a SpriteBatch created for the deferred context.
Dec 2, 2014 at 7:18 AM
ok, thanks

yes, m_Sprites is created with deferred context reference though I use the same variable name pDeviceContext in both threads (I should rename to avoid confusion)

What I do not understand is what initialisation needs to be done on the deferred context. None or same as on the immediate. For example, in your Begin implementation, you init all necesarry steps and require preceding call to RSSetViewports no matter if it is deferred or immediate, however I am not sure if this is needed for deferred context ... That confuses me. In the MSDN I read that deferred context will kind of record all operations that would normally be done on the immediate context, however I have not found whether you need or not to "prepare" the context as you normally do on the immediate one. Anyway, I will continue looking for working solution, it's just with my limited DX11 expertise it's kind of difficult...

As for the performance win I have no doubt. The textures are dynamicly generated by separate process and the main rendering do not explicitly need to wait for them. If they are not availabe yet, no problem, I want to show them once thay are prepared in the CPU memory. For that scenatrio, deferred context is an answer.
Dec 2, 2014 at 8:46 AM
I just studied a bit by reading the following topic:

http://www.gamedev.net/topic/641894-multithreading-and-id3d11devicecontextmap/

and learned that I could simply Map memcpy and Unmap me resource ONLY in deferred context and then use FinishCommandList to put it in the queue and then apply the changes in the immediate context with ExecuteCommandList first and then use SpriteBatch Begin(), Draw(), End() on the immediate context.

It means I would only need one SpriteBatch instance directly on immediate context.

I will give it a try and post the results soon.
Coordinator
Dec 2, 2014 at 7:11 PM
Edited Dec 2, 2014 at 8:02 PM
I've confirmed that DirectXTK works fine with deferred contexts with a quick test.

You have to set all the state on a deferred context, every frame, including render target and viewport.

Good luck!
Marked as answer by walbourn on 12/2/2014 at 1:02 PM
Dec 3, 2014 at 9:03 AM
Ok, thanks. It was not clear for me whether you need or not to initiate the deferred context as the commands are executed on the immediate one. Anyway it looks I did not have the target prepared and that caused the texture not being rendered. It is because I have the target only on the main thread that is given to me in the render callback function by the 3D application I am integrating with.

I found the solution as in my previous post. Map(), memcpy and Unmap() done on deferred context prepare the texture data which is then applied during ExecuteCommandList. Then I do the texture rendering with SpriteBatch initated with immediate context. And it works. Still have some synchro work to do but the texture is rendered correctly.
Coordinator
Dec 3, 2014 at 6:08 PM
Edited Dec 3, 2014 at 6:10 PM
Direct3D 11 command lists have no concept of state inheritance. From ID3D11DeviceContext::FinishCommandList on MSDN:
Immediate context state is cleared before and after a command list is executed. A command list has no concept of inheritance. Each call to FinishCommandList will record only the state set since any previous call to FinishCommandList().
This means that the immediate context has the equivalent of ClearState() before the ExecuteCommandList() and then ClearState() afterwards, and the deferred command list itself basically has the equivalent of ClearState() after each FinishCommandList().

Hope the solution works out, but be sure to compare it to a solution that doesn't use deferred contexts/command lists to make sure it's worth all the extra complexity.