Fin*_*man 5 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();
}
}
Run Code Online (Sandbox Code Playgroud)
不会抛出任何异常,根本不会渲染窗口。应用程序运行,但窗口不显示。我不认为这会起作用,因为没有游戏循环并且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);
}
}
Run Code Online (Sandbox Code Playgroud)
即使应用程序正在运行并且没有崩溃,仍然没有显示窗口。
最后我尝试了同样的事情但没有处理CompositionTarget.Rendering,而是使用 DispatcherTimer 代替,并从其Tick事件处理程序内部调用渲染。相同的结果:没有窗口。
任何人都可以指出我正确的方向吗?
我知道这是一篇旧帖子,但对于那些寻找解决方案的人来说,这是我找到的。该解决方案基于 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;
}
Run Code Online (Sandbox Code Playgroud)
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]);
}
}
Run Code Online (Sandbox Code Playgroud)
3. CompositionTarget Rendering:非常简单
private void CompositionTarget_Rendering(object sender, EventArgs e) {
DxImage.RequestRender();
}
Run Code Online (Sandbox Code Playgroud)
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);
}
Run Code Online (Sandbox Code Playgroud)
奖金
您还可以在没有合成目标事件的情况下进行渲染。为此,在 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);
Run Code Online (Sandbox Code Playgroud)
我创建了 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);
}
}
Run Code Online (Sandbox Code Playgroud)
事实上它们是相同的。然后,渲染后,我将 Dx12 RenderTarget 复制到 Dx11 RenderTarget。
var ptr = GetDx12ResourceFromHandle(Resources.Dx11Texture.NativePointer);
commandList.CopyResource(ptr, Resources.RenderTarget);
Run Code Online (Sandbox Code Playgroud)
在我的 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();
}));
}
Run Code Online (Sandbox Code Playgroud)