Rendering on a WPF Control with DirectX 11

7.5k Views Asked by At

I am trying to create a map editor based on WPF. Currently I'm using a hack to render DirectX contents. I created a WinFormsHost and rendered on a WinForms-Panel.

This all because DirectX (I´m using DirectX 11 with Featurelevel 10) wants a Handle (alias IntPtr) where to render. I don´t know how I can initialize and use the DX Device without a handle.

But a WPF control has no handle. So I just found out, there is an interop class called "D3DImage". But I don't understand how to use it.

My current system works like this:

The inner loop goes through a list of "IGameloopElement"s. For each, it renders its content calling "Draw()". After that, it calls "Present()" of the swap chain to show the changes. Then it resets the device to switch the handle to the next element (mostly there is only one element).

Now, because D3DImage doesn't have a handle, how do I render onto it? I just know I have to use "Lock()" then "SetBackBuffer()", "AddDirtyRect()" and then "Unlock()".

But how do I render onto a DirectX11.Texture2D object without specifying a handle for the device?

I´m really lost... I just found the "DirectX 4 WPF" sample on codeplex, but this implements all versions of DirectX, manages the device itself and has such a huge overhead. I want to stay at my current system. I´m managing the device by myself. I don´t want the WPF control to handle it.

The loop just should call "Render()" and then passes the backbuffer texture to the WPF control.

Could anyone tell me how to do this? I´m totally stuck ...

Thanks a lot :)

R

2

There are 2 best solutions below

1
On

Another answer wrote, "D3DImage only supports Direct3D9/Direct3D9Ex"... which is perhaps not entirely true for the last few years anyway. As I summarized in a comment here, the key appears to be that Direct3D11 with DXGI has a very specific interop compatibility mode (D3D11_SHARED_WITHOUT_MUTEX flag) which makes the ID3D11Texture2D1 directly usable as a D3DResourceType.IDirect3DSurface9, without copying any bits, which just so happens to be exactly (and only) what WPF D3DImage is willing to accept.

This is a rough sketch of what worked for me, to create a D3D11 SampleAllocator that produces ID3D11Texture2D1 that are directly compatible with WPF's Direct3D9. Because all the .NET interop shown here is of my own design, this will not be totally ready-to-run code to drop in your project, but the method, intent, and procedures should be clear for easy adaptation.

1. preliminary helper

static D3D_FEATURE_LEVEL[] levels =
{
    D3D_FEATURE_LEVEL._11_1,
    D3D_FEATURE_LEVEL._11_0,
};

static IMFAttributes GetSampleAllocatorAttribs()
{
    MF.CreateAttributes(out IMFAttributes attr, 6);
    attr.SetUINT32(in MF_SA_D3D11_AWARE, 1U);
    attr.SetUINT32(in MF_SA_D3D11_BINDFLAGS, (uint)D3D11_BIND.RENDER_TARGET);
    attr.SetUINT32(in MF_SA_D3D11_USAGE, (uint)D3D11_USAGE.DEFAULT);
    attr.SetUINT32(in MF_SA_D3D11_SHARED_WITHOUT_MUTEX, (uint)BOOL.TRUE);
    attr.SetUINT32(in MF_SA_BUFFERS_PER_SAMPLE, 1U);
    return attr;
}

static IMFMediaType GetMediaType()
{
    MF.CreateMediaType(out IMFMediaType mt);
    mt.SetUINT64(in MF_MT_FRAME_SIZE, new SIZEU(1920, 1080).ToSwap64());
    mt.SetGUID(in MF_MT_MAJOR_TYPE, in WMMEDIATYPE.Video);
    mt.SetUINT32(in MF_MT_INTERLACE_MODE, (uint)MFVideoInterlaceMode.Progressive);
    mt.SetGUID(in MF_MT_SUBTYPE, in MF_VideoFormat.RGB32);
    return mt;
}

2. the D3D11 device and context instances go somewhere

ID3D11Device4 m_d3D11_device;

ID3D11DeviceContext2 m_d3D11_context;

3. initialization code is next

void InitialSetup()
{
    D3D11.CreateDevice(
        null,
        D3D_DRIVER_TYPE.HARDWARE,
        IntPtr.Zero,
        D3D11_CREATE_DEVICE.BGRA_SUPPORT,
        levels,
        levels.Length,
        D3D11.SDK_VERSION,
        out m_d3D11_device,
        out D3D_FEATURE_LEVEL _,
        out m_d3D11_context);

    MF.CreateDXGIDeviceManager(out uint tok, out IMFDXGIDeviceManager m_dxgi);

    m_dxgi.ResetDevice(m_d3D11_device, tok);

    MF.CreateVideoSampleAllocatorEx(
        ref REFGUID<IMFVideoSampleAllocatorEx>.GUID,
        out IMFVideoSampleAllocatorEx sa);

    sa.SetDirectXManager(m_dxgi);

    sa.InitializeSampleAllocatorEx(
        PrerollSampleSink.QueueMax,
        PrerollSampleSink.QueueMax * 2,
        GetSampleAllocatorAttribs(),
        GetMediaType());
}

4. use sample allocator to repeatedly generate textures, as needed

ID3D11Texture2D1 CreateTexture2D(SIZEU sz)
{
    var vp = new D3D11_VIEWPORT
    {
        TopLeftX = 0f,
        TopLeftY = 0f,
        Width = sz.Width,
        Height = sz.Height,
        MinDepth = 0f,
        MaxDepth = 1f,
    };

    m_d3D11_context.RSSetViewports(1, ref vp);

    var desc = new D3D11_TEXTURE2D_DESC1
    {
        SIZEU = sz,
        MipLevels = 1,
        ArraySize = 1,
        Format = DXGI_FORMAT.B8G8R8X8_UNORM,
        SampleDesc = new DXGI_SAMPLE_DESC { Count = 1, Quality = 0 },
        Usage = D3D11_USAGE.DEFAULT,
        BindFlags = D3D11_BIND.RENDER_TARGET | D3D11_BIND.SHADER_RESOURCE,
        CPUAccessFlags = D3D11_CPU_ACCESS.NOT_REQUESTED,
        MiscFlags = D3D11_RESOURCE_MISC.SHARED,
        TextureLayout = D3D11_TEXTURE_LAYOUT.UNDEFINED,
    };

    m_d3D11_device.CreateTexture2D1(ref desc, IntPtr.Zero, out ID3D11Texture2D1 tex2D);
    return tex2D;
}
1
On

WPF's D3DImage only supports Direct3D9/Direct3D9Ex, it does not support Direct3D 11. You need to use DXGI Surface Sharing to make it work.