WPF 利用 Vortice 在 D3DImage 体现 D2D 内容 [复制链接]
发表于 2026-1-28 22:21:24 | 显示全部楼层 |阅读模式
本文绝大部门代码泉源于 Raspberry Monster 同伴提供。我只是代为纪录的工具人
本文是渲染干系系列博客中的一篇,该系列博客已按照逻辑次序编排,方便各人依次阅读。本文属于系列博客中,比力靠前的博客,可以独立阅读,无上下篇依靠。如您对渲染干系感爱好,可以通过以下链接访问整个系列:渲染干系系列博客导航
在开始聊 Vortice 之前,必须要先聊聊 SharpDx 库。 众所周知,如今 SharpDx 已不维护,只管 SharpDx 的不维护对咱开发影响很小,除非必要用到这几年新加的功能,否则利用不维护的 SharpDx 的题目也不大。而 Vortice 是作为 SharpDx 的一个代替的存在,是从 SharpDx 的底子上,继承开发的一个项目。利用 Vortice 底层库,能让 C# 代码比力方便的和 DirectX 对接
在本文这里,将实行利用 Vortice 库使得 D2D 绘制的内容可以或许在 D3DImage 上体现。本文将必要用到 WPF 的内建机制。如果各人盼望看到最裸的无 UI 框架依靠的实现,还请参阅 DirectX 利用 Vortice 从零开始控制台创建 Direct2D1 窗口
准备步调

新建一个空 WPF 项目,按照 .NET 的惯例,通过 NuGet 安装好 Vortice 的库,安装完成之后的 csproj 项目文件代码大概如下
  1. <Project Sdk="Microsoft.NET.Sdk">
  2.   <PropertyGroup>
  3.     <OutputType>WinExe</OutputType>
  4.     <TargetFramework>net9.0-windows</TargetFramework>
  5.     <Nullable>enable</Nullable>
  6.     <ImplicitUsings>enable</ImplicitUsings>
  7.     <UseWPF>true</UseWPF>
  8.         <AllowUnsafeBlocks>True</AllowUnsafeBlocks>
  9.   </PropertyGroup>
  10.   <ItemGroup>
  11.     <PackageReference Include="Vortice.Direct2D1" Version="3.6.2" />
  12.     <PackageReference Include="Vortice.Direct3D11" Version="3.6.2" />
  13.     <PackageReference Include="Vortice.Direct3D9" Version="3.6.2" />
  14.     <PackageReference Include="Vortice.Wpf" Version="3.6.2" />
  15.   </ItemGroup>
  16. </Project>
复制代码
添加界面代码

修改 MainWindow.xaml 添加一点界面代码
  1.     <Grid>
  2.         <Border Margin="10,10,10,10" BorderBrush="Black" BorderThickness="1">
  3.             <Image x:Name="Image" Width="600" Height="500" Stretch="Fill">
  4.                 <Image.Source>
  5.                     <interop:D3DImage x:Name="D3DImage" />
  6.                 </Image.Source>
  7.             </Image>
  8.         </Border>
  9.     </Grid>
复制代码
进入到 MainWindow.xaml.cs 文件,开始编写本文的核心代码
代码必要放在 WPF 窗口获取句柄之后,简朴方便来说在 Loaded 之后就可以了
  1.         public MainWindow()
  2.         {
  3.             InitializeComponent();
  4.             Loaded += MainWindow_Loaded;
  5.         }
  6.         private void MainWindow_Loaded(object sender, RoutedEventArgs e)
  7.         {
  8.             ...
  9.         }
复制代码
创建 D3D 装备

直接利用 D3D11.D3D11CreateDevice 方法即可创建 ID3D11Device 装备
  1.             ID3D11Device device =
  2.                 D3D11.D3D11CreateDevice(Vortice.Direct3D.DriverType.Hardware, DeviceCreationFlags.BgraSupport);
复制代码
对比 SharDx 的是直接 new 创建出来的,在 Vortice 里是接纳静态工厂创建的,其头脑没有本质差别
创建出来的装备可用来创建纹理,创建出来的纹理终极将会转换,作为 D3DImage 的 SetBackBuffer 所需的参数设置进去
创建纹理必要一些参数,如宽度高度信息。本文这里直接接纳界面的 Image 控件的宽度高度作为纹理的宽度高度。无需缩放的纹理可以得到更高的渲染性能
  1.             var desc = new Texture2DDescription()
  2.             {
  3.                 BindFlags = BindFlags.RenderTarget | BindFlags.ShaderResource,
  4.                 Format = DXGIFormat.B8G8R8A8_UNorm,
  5.                 Width = width,
  6.                 Height = height,
  7.                 MipLevels = 1,
  8.                 SampleDescription = new SampleDescription(1, 0),
  9.                 Usage = ResourceUsage.Default,
  10.                 MiscFlags = ResourceOptionFlags.Shared,
  11.                 CPUAccessFlags = CpuAccessFlags.None,
  12.                 ArraySize = 1
  13.             };
  14.             ID3D11Texture2D renderTarget = device.CreateTexture2D(desc);
复制代码
以上参数关键点除了尺寸信息之外,还在于颜色格式选用 B8G8R8A8_UNorm 格式。这是最通用的格式,可以很方便很设置给到 D3DImage 里
对接 D2D 渲染

创建出来的纹理可先和 D2D 举行对接,也可先和 D3DImage 对接,这个次序可随意分列。本文这里先和 D2D 举行对接
和 D2D 对接时,必要先将 ID3D11Texture2D 当成 IDXGISurface 才气让 D2D 将画面绘制在纹理上,代码如下
  1.             var surface = renderTarget.QueryInterface<IDXGISurface>();
复制代码
在利用 D2D 之前,按照 DirectX 惯例,先创建工厂,代码如下
  1.             var d2dFactory = D2D1.D2D1CreateFactory<ID2D1Factory>();
复制代码
天然,在 SharpDx 内里,是直接用 new 创建出来,代码如下
  1. // 以下是 SharpDx 的代码:
  2.             var d2DFactory = new D2D.Factory();
复制代码
设置创建渲染的信息,代码如下
  1.             var renderTargetProperties =
  2.                 new RenderTargetProperties(new Vortice.DCommon.PixelFormat(DXGIFormat.B8G8R8A8_UNorm,
  3.                     Vortice.DCommon.AlphaMode.Premultiplied));
复制代码
以上代码关键在于 B8G8R8A8_UNorm 像素格式。别的的 AlphaMode.Premultiplied 为像素预乘,简朴来说就是终极输出的值里的 RGB 分量都乘以透明度。更多细节请参阅 支持的像素格式和 Alpha 模式 - Win32 apps - Microsoft Learn
完成设置之后,即可创建 ID2D1RenderTarget 对象,代码如下
  1.         private ID2D1RenderTarget? _d2DRenderTarget;
  2. _d2DRenderTarget = d2dFactory.CreateDxgiSurfaceRenderTarget(surface, renderTargetProperties);
复制代码
对接 D3DImage 设置指针

创建 SetRenderTarget 方法,将传入的 ID3D11Texture2D 参数和 D3DImage 绑定,方法署名如下
  1.         private void SetRenderTarget(ID3D11Texture2D target)
  2.         {
  3.             ...
  4.         }
复制代码
先将像素格式举行映射,这个过程中只是摆列范例界说不雷同而已,过程中不会发生任何的像素变动,也就没有实际的性能消耗
  1.         private static Vortice.Direct3D9.Format TranslateFormat(ID3D11Texture2D texture)
  2.         {
  3.             switch (texture.Description.Format)
  4.             {
  5.                 case DXGIFormat.R10G10B10A2_UNorm:
  6.                     return Vortice.Direct3D9.Format.A2B10G10R10;
  7.                 case DXGIFormat.R16G16B16A16_Float:
  8.                     return Vortice.Direct3D9.Format.A16B16G16R16F;
  9.                 case DXGIFormat.B8G8R8A8_UNorm:
  10.                     return Vortice.Direct3D9.Format.A8R8G8B8;
  11.                 default:
  12.                     return Vortice.Direct3D9.Format.Unknown;
  13.             }
  14.         }
复制代码
大概有同伴感到狐疑,为什么在 DXGI 内里是按照 B8G8R8A8 分列的,而在 D3D9 是按照 A8R8G8B8 分列的,这两个是否是逆序关系?着实不是的,这两个颜色格式从内存的角度来说是完全雷同的。仅仅只是由于•  DXGI 名称按 内存字节次序(小端) 形貌 B, G, R, A 每个 8 位。而 D3D9 名称按 通道意义(从高位到低位) 形貌 A, R, G, B 通道
在 C# 内里默认也接纳小端次序,这时间就和 DXGI 形貌的更贴合,从内存的角度上讲,一个 32bit 的像素颜色分量如下
  1. byte0 = B
  2. byte1 = G
  3. byte2 = R
  4. byte3 = A
复制代码
按照 DirectX 的发起,保举利用 BGRA 格式而不是 RGBA 格式,在底层实现内里,会让 BGRA 格式目标的性能优于 RGBA 格式。具体请看 支持的像素格式和 Alpha 模式 - Win32 apps - Microsoft Learn
转换颜色格式摆列后,再获取纹理的共享句柄,代码如下
  1.         private IntPtr GetSharedHandle(ID3D11Texture2D texture)
  2.         {
  3.             using (var resource = texture.QueryInterface<IDXGIResource>())
  4.             {
  5.                 return resource.SharedHandle;
  6.             }
  7.         }
复制代码
为了创建 D3D9 装备,还必要更多准备工作,如准备好参数,代码如下
  1.         private static Vortice.Direct3D9.PresentParameters GetPresentParameters()
  2.         {
  3.             var presentParams = new Vortice.Direct3D9.PresentParameters();
  4.             presentParams.Windowed = true;
  5.             presentParams.SwapEffect = Vortice.Direct3D9.SwapEffect.Discard;
  6.             presentParams.DeviceWindowHandle = NativeMethods.GetDesktopWindow();
  7.             presentParams.PresentationInterval = PresentInterval.Default;
  8.             return presentParams;
  9.         }
  10. public static class NativeMethods
  11. {
  12.     [DllImport("user32.dll", SetLastError = false)]
  13.     public static extern IntPtr GetDesktopWindow();
  14. }
复制代码
完成准备工作,开始创建 D3D9 装备,代码如下
  1.             var format = TranslateFormat(target);
  2.             var handle = GetSharedHandle(target);
  3.             var presentParams = GetPresentParameters();
  4.             var createFlags = CreateFlags.HardwareVertexProcessing | CreateFlags.Multithreaded |
  5.                               CreateFlags.FpuPreserve;
  6.             var d3DContext = D3D9.Direct3DCreate9Ex();
  7.             // 以下代码强行获取第 0 个适配器,可能会在多显卡等情况下导致问题。如设置 CPU 的 CpuAccessFlags 为 Read 等无权限问题
  8.             using IDirect3DDevice9Ex d3DDevice =
  9.                 d3DContext.CreateDeviceEx(adapter: 0, DeviceType.Hardware, focusWindow: IntPtr.Zero, createFlags, presentParams);
  10.             _d3D9Device = d3DDevice;
复制代码
让 D3D9 装备,从 GetSharedHandle 方法获取到的共享纹理创建 D3D9 的纹理,从而可以让创建出来的 D3D9 的纹理设置到 D3DImage 上
  1.         private IDirect3DTexture9? _renderTarget;
  2.         private void SetRenderTarget(ID3D11Texture2D target)
  3.         {
  4.             ...
  5.             _renderTarget = d3DDevice.CreateTexture(target.Description.Width, target.Description.Height, 1,
  6.                 Vortice.Direct3D9.Usage.RenderTarget, format, Pool.Default, ref handle);
  7.             ...
  8.         }
复制代码
从 IDirect3DTexture9 取出外貌,设置给到 D3DImage 上
  1.             using var surface = _renderTarget.GetSurfaceLevel(0);
  2.             D3DImage.Lock();
  3.             D3DImage.SetBackBuffer(D3DResourceType.IDirect3DSurface9, surface.NativePointer,
  4.                 enableSoftwareFallback: true);
  5.             D3DImage.Unlock();
复制代码
云云即可完成关联对接工作。简朴来说就是让 D3D11 和 D2D 对接,让 D3D11 和 DXGI 对接,再让 DXGI 和 D3D9 对接。渲染部门就拿 ID3D11Texture2D 共享纹理给到 D3D9 的 d3DDevice.CreateTexture 转换为 IDirect3DTexture9 纹理。整个过程就是让 D2D 绘制在 ID3D11Texture2D 纹理上,再将 ID3D11Texture2D 纹理当成 IDirect3DTexture9 给 D3DImage 利用,如下图所示

渲染画面

监听 WPF 的 CompositionTarget.Rendering 变乱,在此变乱内里完成 D2D 的画面渲染
  1.             CompositionTarget.Rendering += CompositionTarget_Rendering;
  2.         private void CompositionTarget_Rendering(object? sender, EventArgs e)
  3.         {
  4.             if (_d2DRenderTarget is null)
  5.             {
  6.                 return;
  7.             }
  8.             _d2DRenderTarget.BeginDraw();
  9.             OnRender(_d2DRenderTarget);
  10.             _d2DRenderTarget.EndDraw();
  11.             _d3D11Device?.ImmediateContext.Flush();
  12.             D3DImage.Lock();
  13.             D3DImage.AddDirtyRect(new Int32Rect(0, 0, D3DImage.PixelWidth, D3DImage.PixelHeight));
  14.             D3DImage.Unlock();
  15.             Image.InvalidateVisual();
  16.         }
复制代码
在 OnRender 方法内里,仅仅只面向 D2D 举行渲染,逻辑非常简朴。可以实行本身编写悦目标渲染画面,本文这里只是做简朴的矩形动画,代码如下
[code]        private void OnRender(ID2D1RenderTarget renderTarget)        {            using var brush = renderTarget.CreateSolidColorBrush(new Color4(Random.Shared.Next() | 0xFF = maxX || _x = maxY || _y

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
回复

使用道具 举报

登录后关闭弹窗

登录参与点评抽奖  加入IT实名职场社区
去登录
快速回复 返回顶部 返回列表