有没有办法在WPF窗口中托管DirectX12应用程序?

时间:2016-06-06 21:57:55

标签: c# wpf interop directx-12

我知道这个问题的术语一定是错的,但请耐心地试着从外行人的角度看问题(我没有计算机技术的形成,我自己我从编程语言的正规教育中得到的最接近的是我学校的机器人俱乐部。

我想要的是能够使用托管的DirectX 12作为"背景"我的应用程序,游戏循环和所有。并且,如果可能的话,能够在实际的DirectX游戏周围使用功能区或工具箱或菜单等WPF控件。我一直在寻找互联网,而我发现的只是Windows和DirectX 9.0的旧东西;我希望这些日子里有新的东西。

我尝试了Windows Form方法,基本上就是这样:

using System;
using System.Windows;
using System.Windows.Interop;
using Microsoft.DirectX.Direct3D;
using DColor = System.Drawing.Color;

public partial class MainWindow : Window
{
    Device device;
    public MainWindow()
    {
        InitializeComponent();
        initDevice();
    }

    private void initDevice()
    {
        try
        {
            PresentParameters parameters = new PresentParameters();
            parameters.Windowed = true;
            parameters.SwapEffect = SwapEffect.Discard;
            IntPtr windowHandle = new WindowInteropHelper(this).Handle;

            device = new Device(0, DeviceType.Hardware, windowHandle, CreateFlags.HardwareVertexProcessing, parameters);
        }
        catch(Exception e)
        {
            MessageBox.Show("initDevice threw an Exception\n" + e.Message, "ERROR", MessageBoxButton.OK, MessageBoxImage.Error);
        }
    }

    private void render()
    {
        device.Clear(ClearFlags.Target, DColor.LightGreen, 0f, 1);
        device.Present();
    }
}

不抛出异常,窗口根本不会渲染。应用程序运行,但窗口没有显示。我没想到这会起作用,因为没有游戏循环而render无法从任何地方调用,但我没想到窗口甚至没有被显示。如果我注释掉调用initDevice()的行,WPF的空白窗口会正常显示

然后我发现CompositionTarget.Rendering事件每帧被调用一次(或勾选?),因此必须将此事件的处理程序用作游戏循环。

所以我尝试了这个:

using System;
using System.Drawing;
using System.IO;
using System.Windows;
using System.Windows.Media;
using System.Windows.Forms.Integration;
using Microsoft.DirectX.Direct3D;
using DColor = System.Drawing.Color;
using System.Windows.Forms;

public partial class MainWindow : Window
{
    Device device = null;
    MemoryStream stream;
    PictureBox display;
    WindowsFormsHost host;

    public MainWindow()
    {
        InitializeComponent();
        initDevice();
        CompositionTarget.Rendering += CompositionTarget_Rendering;
    }

    private void CompositionTarget_Rendering(object sender, EventArgs e)
    {
        render();
    }

    private void initDevice()
    {
        try
        {
            PresentParameters parameters = new PresentParameters();
            parameters.Windowed = true;
            parameters.SwapEffect = SwapEffect.Discard;

            device = new Device(0, DeviceType.Hardware, display, CreateFlags.HardwareVertexProcessing, parameters);
            stream = new MemoryStream();
            device.SetRenderTarget(0, new Surface(device, stream, Pool.Managed));
        }
        catch(Exception e)
        {
            System.Windows.MessageBox.Show("initDevice threw an Exception\n" + e.Message, "ERROR", MessageBoxButton.OK, MessageBoxImage.Error);
        }
    }

    private void render()
    {
        device.Clear(ClearFlags.Target, DColor.LightGreen, 0f, 1);
        device.Present();
        display.Image = Image.FromStream(stream);
    }

    private void Window_Loaded(object sender, RoutedEventArgs e)
    {
        host = new WindowsFormsHost();
        display = new PictureBox();
        host.Child = display;
        mainGrid.Children.Add(host);
    }
}

即使应用程序正在运行且没有崩溃,仍然没有显示窗口。

最后我尝试了同样的事情,但没有处理CompositionTarget.Rendering,而是使用DispatcherTimer,并从其Tick事件处理程序中调用render。相同的结果:没有窗口。

有人能指出我正确的方向吗?

2 个答案:

答案 0 :(得分:2)

project应该会有所帮助。它目前仅支持Direct3D 11,但DirectX 12的原理相同。

  

那就是说,为什么你需要DirectX 12而不仅仅是坚持使用DirectX 11?答案应该比“12大于11”更具技术性。

答案 1 :(得分:2)

我知道这是一个老帖子,但对于那些搜索解决方案的人来说,有一个我找到的。 该解决方案基于Chuck提到的项目中的D3D11Image。

<强> 1。在Window_Loaded_Event:

    private void Window_Loaded(object sender, RoutedEventArgs e) {
        InitDx12();
        CreateDx11Stuff();

        DxImage.SetPixelSize(1280, 720);
        DxImage.WindowOwner = (new System.Windows.Interop.WindowInteropHelper(this)).Handle;
        DxImage.OnRender += Render;
        CompositionTarget.Rendering += CompositionTarget_Rendering;
    }

<强> 2。创建Dx11资料:

private void CreateDx11Stuff() {
        D3D11Device = SharpDX.Direct3D11.Device.CreateFromDirect3D12(D3D12Device, SharpDX.Direct3D11.DeviceCreationFlags.BgraSupport | SharpDX.Direct3D11.DeviceCreationFlags.Debug, new[] { SharpDX.Direct3D.FeatureLevel.Level_12_1 }, Adatper, CommandQueue);

        D3D11On12 = ComObject.QueryInterfaceOrNull<SharpDX.Direct3D11.Device11On12>(D3D11Device.NativePointer);                       

        for(int idx = 0; idx < BackBufferCount; idx++) {
            D3D11On12.CreateWrappedResource(BackBuffers[idx], new D3D11ResourceFlags { BindFlags = (int)BindFlags.RenderTarget, CPUAccessFlags = 0, MiscFlags = (int)0x2L, StructureByteStride = 0 }, (int)ResourceStates.RenderTarget, (int)ResourceStates.Present, typeof(Texture2D).GUID, out D3D11BackBuffers[idx]);
        }
    }

第3。 CompositionTarget渲染:非常简单

private void CompositionTarget_Rendering(object sender, EventArgs e) {
        DxImage.RequestRender();
    }

<强> 4。渲染功能:

private void Render(IntPtr surface, bool newSurface) {
        DoDx12Rendering();

        var unk = new ComObject(surface);
        var dxgiRes = unk.QueryInterface<SharpDX.DXGI.Resource>();

        var tempRes = D3D11Device.OpenSharedResource<SharpDX.Direct3D11.Resource>(dxgiRes.SharedHandle);
        var backBuffer = tempRes.QueryInterface<Texture2D>();
        var d3d11BackBuffer = D3D11BackBuffers[CurrentFrame];

        D3D11On12.AcquireWrappedResources(new[] { d3d11BackBuffer }, 1);
        D3D11Device.ImmediateContext.CopyResource(d3d11BackBuffer, backBuffer);
        D3D11Device.ImmediateContext.Flush();
        D3D11On12.ReleaseWrappedResources(new[] { d3d11BackBuffer }, 1);
    }

<强>加成

您也可以在没有合成目标事件的情况下进行渲染。 为此,在Render回调中 - &gt; void Render(IntPtr surface,bool newSurface),只存储表面的句柄。

为此调用DxImage.RequestRender()。

您是否在渲染循环中渲染并在最后将D3D11on12添加到D3D11副本。

注意

如果您处理resize事件,请考虑使用DxImage.SetPixelSize调整DxImage的大小,然后重新创建包装的资源。

更多说明

我像这样创建设备:

_D3D9Device = new DeviceEx(new Direct3DEx(), 0, DeviceType.Hardware, handle, CreateFlags.HardwareVertexProcessing | CreateFlags.Multithreaded | CreateFlags.FpuPreserve, new SharpDX.Direct3D9.PresentParameters(1, 1) {
            Windowed = true,
            SwapEffect = SharpDX.Direct3D9.SwapEffect.Discard,
            DeviceWindowHandle = handle,
            PresentationInterval = PresentInterval.Immediate
        });


_D3D11Device = SharpDX.Direct3D11.Device.CreateFromDirect3D12(Device, DeviceCreationFlags.BgraSupport, new[] { SharpDX.Direct3D.FeatureLevel.Level_12_0 }, null, RenderCommandQueue);

我创建了Dx11和Dx9 FBO:

private void CreateWPFInteropFBO()
    {
        var desc = new Texture2DDescription {
            ArraySize = 1,
            BindFlags = BindFlags.RenderTarget,
            Format = SharpDX.DXGI.Format.B8G8R8A8_UNorm,
            Height = RenderTargetSize.Height,
            Width = RenderTargetSize.Width,
            MipLevels = 1,
            OptionFlags = ResourceOptionFlags.Shared,
            SampleDescription = new SharpDX.DXGI.SampleDescription(1, 0),
            Usage = ResourceUsage.Default
        };

        Dx11Texture?.Dispose();

        Dx11Texture = new Texture2D(_D3D11Device, desc);

        var ptr = Dx11Texture.NativePointer;
        var comobj = new ComObject(ptr);
        using (var dxgiRes = comobj.QueryInterface<SharpDX.DXGI.Resource>()) {
            var sharedHandle = dxgiRes.SharedHandle;

            var texture = new Texture(_D3D9Device, desc.Width, desc.Height, 1, SharpDX.Direct3D9.Usage.RenderTarget, SharpDX.Direct3D9.Format.A8R8G8B8, Pool.Default, ref sharedHandle);

            Dx9Surface?.Dispose();
            Dx9Surface = texture.GetSurfaceLevel(0);
        }
    }
事实上,他们是相同的。 然后,渲染后我将我的Dx12 RenderTarget复制到我的Dx11 RenderTarget。

        var ptr = GetDx12ResourceFromHandle(Resources.Dx11Texture.NativePointer);
        commandList.CopyResource(ptr, Resources.RenderTarget);

在我的RenderLoop中,我像这样更新BackBuffer:

private async void UpdateDx9Image()
    {
        if (Application.Current == null) return;

        await Application.Current?.Dispatcher.BeginInvoke(DispatcherPriority.Render, new Action(() =>
        {
            if (DxImage.TryLock(new Duration(new TimeSpan(0, 0, 0, 0, 16))))
            {
                DxImage.SetBackBuffer(D3DResourceType.IDirect3DSurface9, _Renderer.Resources.Dx9Surface.NativePointer, false);
                DxImage.AddDirtyRect(new Int32Rect(0, 0, _Renderer.Resources.Dx9Surface.Description.Width, _Renderer.Resources.Dx9Surface.Description.Height));
            }

            DxImage.Unlock();
        }));
    }