【问题标题】:Is there a way to host a DirectX12 application inside a WPF window?有没有办法在 WPF 窗口中托管 DirectX12 应用程序?
【发布时间】:2016-10-06 15:43:12
【问题描述】:

我知道这个问题的术语肯定是全错的,但是请大家多多包涵,试着站在我门外汉的角度看问题(我没有计算机技术的形成,我是一个自学成才的爱好者。最接近的我接受过正规的编程语言教育是我学校的机器人俱乐部)。

我想要的是能够将托管 DirectX 12 用作我的应用程序的“背景”,并带有游戏循环等等。并且,如果可能的话,能够拥有 WPF 控件,如功能区或工具箱或实际 directX 游戏周围的菜单。我一直在寻找整个互联网,我发现的只是 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。结果相同:没有窗口。

谁能指出我正确的方向?

【问题讨论】:

    标签: c# wpf interop directx-12


    【解决方案1】:

    我知道这是一篇旧帖子,但对于那些寻找解决方案的人来说,我找到了那个。 该解决方案基于 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) 中,只需存储表面的句柄即可。

    为此调用 DxImage.RequestRender()。

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

    注意

    如果您处理调整大小事件,请考虑使用 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();
            }));
        }
    

    【讨论】:

    • 我有点晚了,但我尝试了你的方法,结果我发现 DX12 向 OpenSharedResource 抱怨,因为 DX11 资源没有使用 Shared 标志创建。事实上,D3DImage11 似乎甚至没有创建 D3D11 资源(它是 D3D10 资源)。你也有这个问题吗?
    • 不。我没问题。
    【解决方案2】:

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

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

    【讨论】:

    • 我有一个非常强大的 GPU,据说 DirectX 12 具有图形库中有史以来最好的性能,我想看看它到底有多好。非常感谢您的链接,看起来很有希望。
    • DirectX 12 具有潜力以获得更好的 CPU 端性能,但它需要大量的工程工作才能实现。如果您需要 Direct3D 硬件功能级别 11.0 或更高版本(DX12 没有任何适用于 FL 9.x 或 10.x 设备的驱动程序),则 DX11 和 DX12 的 GPU 功能相同。一般来说,除非你有一个庞大的图形工程师团队需要 DX12 API 提供的额外控制,否则你应该已经将 DX11 推到了临界点。对于独立开发者或小型团队来说,让 DX 12 开心是一项艰巨的工作。
    • 我想为 DX12 提供模拟功能的唯一原因是我想学习它,而不是现在想用它来玩游戏。所以,如果对 DX12 也有这样的控制,那就太好了。
    • 你能移植项目以支持 D3D12 吗?
    猜你喜欢
    • 1970-01-01
    • 2011-06-29
    • 1970-01-01
    • 1970-01-01
    • 2020-01-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多