c# - 有没有办法在 WPF 窗口内托管 DirectX12 应用程序?

标签 c# wpf interop directx-12

我知道这个问题的术语一定是错误的,但是请耐心等待,尝试从我外行的角度看问题(我对计算机技术没有任何基础,我是一个自学爱好者。最接近的我从正规的编程语言教育中获得的是我学校的机器人俱乐部)。

我想要的是能够使用托管 DirectX 12 作为我的应用程序的“背景”,以及游戏循环等。并且,如果可能的话,能够在实际的 directX 游戏周围拥有 WPF 控件,例如功能区、工具箱或菜单。我一直在互联网上查找,我发现的都是 Windows 和 DirectX 9.0 的非常旧的东西;我希望这些天有新的东西。

我尝试了 Windows 窗体方法,基本上是这样的:

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

不会引发异常,窗口根本不会呈现。应用程序运行,但窗口不显示。我认为这行不通,因为没有游戏循环,并且渲染不会从任何地方调用,但我没想到窗口甚至没有显示。如果我注释掉调用 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。相同的结果:没有窗口。

谁能给我指出正确的方向吗?

最佳答案

我知道这是一篇旧帖子,但对于那些寻找解决方案的人来说,这是我找到的。 该解决方案基于 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 回调 --> void Render(IntPtr surface, bool newSurface) 中,只需存储 Surface 的句柄。

为此调用 DxImage.RequestRender()。

您是否在渲染循环中进行渲染并在末尾添加 D3D11on12 到 D3D11 副本。

注意

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

更多说明

我像这样创 build 备:

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

关于c# - 有没有办法在 WPF 窗口内托管 DirectX12 应用程序?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/37667487/

相关文章:

c# - 在 C# 中使用 for 循环逐行更新表格

c# - BinaryWriter Endian 问题

c# - 将数据加载到松散耦合 View 及其 View 模型/模型中的 MVVM 策略

C# 2.0 解析 Excel 电子表格的最快方法

c# - 使用 Parallel.ForEach<T> 设置 Parallel.ForEach 外部的 bool 值

c# - VS2019 Docker 支持和 Dockerfile 失败

wpf - 如何在WPF中水平分布对象数组?

wpf - 为什么我的 GUI 卡住?

c# - 如何使用 office interop API 枚举 word 文档?

C# Marshalling double* 从 C++ DLL?