有没有办法在 WPF window 中托管 DirectX12 应用程序?
Is there a way to host a DirectX12 application inside a WPF window?
我知道这个问题的术语肯定是错误的,但请耐心等待,并尝试从我外行的角度看问题(我对计算机技术没有任何了解,我是一个自学爱好者。我从正规编程语言教育中获得的最接近的是我学校的机器人俱乐部)。
我想要的是能够使用托管 DirectX 12 作为我的应用程序的 "background",具有游戏循环和所有功能。并且,如果可能的话,能够在实际的 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();
}
}
没有异常被抛出,window 根本不会被渲染。应用程序运行,但 window 未显示。我认为这行不通,因为没有游戏循环并且 render
不会从任何地方调用,但我没想到 window 甚至没有显示。如果我注释掉调用 initDevice() 的行,WPF 的空白 window 会正常显示
然后我发现 CompositionTarget.Rendering
事件每帧(或 tick?)被调用一次,所以这个事件的处理程序必须用作游戏循环。
所以我尝试了这个:
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);
}
}
仍然没有显示 window,即使应用程序 运行 并且没有崩溃。
最后我尝试了同样的事情,但没有处理 CompositionTarget.Rendering
,而是使用 DispatcherTimer,并从其 Tick
事件处理程序内部调用了 render。同样的结果:没有 Window.
谁能给我指出正确的方向?
这个 project 应该有所帮助。目前仅支持Direct3D 11,但原理与DirectX 12相同
That said, why do you need DirectX 12 instead of just sticking with DirectX 11? The answer should be something more technical than "12 is bigger than 11."
我知道这是一个旧的 post 但对于那些寻找解决方案的人来说,我找到了一个。
该解决方案基于 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 Rendering : 很简单
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();
}));
}
我知道这个问题的术语肯定是错误的,但请耐心等待,并尝试从我外行的角度看问题(我对计算机技术没有任何了解,我是一个自学爱好者。我从正规编程语言教育中获得的最接近的是我学校的机器人俱乐部)。
我想要的是能够使用托管 DirectX 12 作为我的应用程序的 "background",具有游戏循环和所有功能。并且,如果可能的话,能够在实际的 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();
}
}
没有异常被抛出,window 根本不会被渲染。应用程序运行,但 window 未显示。我认为这行不通,因为没有游戏循环并且 render
不会从任何地方调用,但我没想到 window 甚至没有显示。如果我注释掉调用 initDevice() 的行,WPF 的空白 window 会正常显示
然后我发现 CompositionTarget.Rendering
事件每帧(或 tick?)被调用一次,所以这个事件的处理程序必须用作游戏循环。
所以我尝试了这个:
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);
}
}
仍然没有显示 window,即使应用程序 运行 并且没有崩溃。
最后我尝试了同样的事情,但没有处理 CompositionTarget.Rendering
,而是使用 DispatcherTimer,并从其 Tick
事件处理程序内部调用了 render。同样的结果:没有 Window.
谁能给我指出正确的方向?
这个 project 应该有所帮助。目前仅支持Direct3D 11,但原理与DirectX 12相同
That said, why do you need DirectX 12 instead of just sticking with DirectX 11? The answer should be something more technical than "12 is bigger than 11."
我知道这是一个旧的 post 但对于那些寻找解决方案的人来说,我找到了一个。 该解决方案基于 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 Rendering : 很简单
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();
}));
}