WPF 使用 Silk.NET 进行 DirectX 渲染入门

2021-12-27 09:03:26 浏览数 (1)

本文告诉大家如何使用 dotnet 基金会新开源的 Silk.NET 库调用 DirectX 进行渲染的方法。此库是对 DirectX 的底层基础封装,用上了 dotnet 和 C# 的各个新特性,相对来说基础性能较好,也许后续可以考虑作为 SharpDx 的代替

本文将告诉大家如何使用 Silk.NET 创建 DirectX 的各个对象,进行初始化逻辑,再对接 Direct2D 进行界面绘制。当前是 2021.12.23 此时 Silk.NET 还没有完成 Direct2D 的封装,为了方便演示,本文使用了 SharpDx 的 D2D 代替

本文非新手友好,如果是刚接触 DirectX 那我推荐先阅读 WPF 使用 SharpDx 渲染博客导航

当前 SharpDx 已不维护,我正在找代替的项目,详细请看 SharpDx 的代替项目

刚好找到了 dotnet 基金会下的 Silk.NET 库,此库是新写的,用上了很多 dotnet 和 C# 的新特性,例如通过 COM 调用 DirectX 的实现逻辑是通过了 delegate* unmanaged 新特性,这是 C# 9 的新特性,请看 Function pointers - C# 9.0 draft specifications Microsoft Docs

代码的写法如下

代码语言:javascript复制
        public ID3D11Device
        (
            void** lpVtbl = null
        ) : this()
        {
            if (lpVtbl is not null)
            {
                LpVtbl = lpVtbl;
            }
        }

        public void** LpVtbl;

        public readonly unsafe int QueryInterface(Guid* riid, void** ppvObject)
        {
            var @this = (ID3D11Device*) Unsafe.AsPointer(ref Unsafe.AsRef(in this));
            int ret = default;
            ret = ((delegate* unmanaged[Cdecl]<ID3D11Device*, Guid*, void**, int>)LpVtbl[0])(@this, riid, ppvObject);
            return ret;
        }

通过以上的代码,特别是 ((delegate* unmanaged[Cdecl]<ID3D12Device*, Guid*, void**, int>)LpVtbl[0])(@this, riid, ppvObject); 这句如此复杂的代码,即可减少 COM 默认 dotnet 封装的 RCW 封装层的封送损耗。当然了,这部分不是本文的重点,细节请看 Runtime Callable Wrapper Microsoft Docs

大家只需要知道,此库的实现里面,可以很大减少调用 COM 时的额外损耗。但这也带来了一点坑,例如调用方也只能采用不安全代码调用,写法也有点诡异

根据 Surface sharing between Windows graphics APIs - Win32 apps 文档,为了在 WPF 的 D3DImage 上进行 D2D 绘制,就需要通过 D3D11 进行转接,好在此转接也只是指针的传输而已,基本没有啥性能损耗。为了在 WPF 上使用到 D2D 就需要执行如下步骤:

  • 创建 D3D11 设备
  • 通过 DXGI 关联 D2D 设备
  • 创建 D3D9 设备

如官方文档的转换图

使用 DirectX 时,初始化参数的代码将会特别多。由于 Silk.NET 只是对 DirectX 的底层封装,没有细节隐藏,也就是说使用过程的复杂度也会特别多

在开始之前,先准备一个空 WPF 项目,基于 dotnet 6 框架。安装好如下库,可编辑 csproj 文件,修改为如下代码

代码语言:javascript复制
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>WinExe</OutputType>
    <TargetFramework>net6.0-windows</TargetFramework>
    <Nullable>enable</Nullable>
    <UseWPF>true</UseWPF>
    <AllowUnsafeBlocks>True</AllowUnsafeBlocks>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="SharpDX.Direct2D1" Version="4.2.0" />
    <PackageReference Include="Silk.NET.Direct3D11" Version="2.11.0" />
    <PackageReference Include="Silk.NET.Direct3D9" Version="2.11.0" />
    <PackageReference Include="Silk.NET.DXGI" Version="2.11.0" />
  </ItemGroup>

</Project>

以上代码关键在于 AllowUnsafeBlocks 需要开启,为了使用 Silk.NET 开启不安全代码。当前 Silk.NET 还没有完成 D2D 封装,本文将使用 SharpDX.Direct2D1 库

在 XAML 界面添加 D3DImage 如下面代码

代码语言:javascript复制
<Window x:Class="RawluharkewalQeaninanel.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:RawluharkewalQeaninanel"
        xmlns:interop="clr-namespace:System.Windows.Interop;assembly=PresentationCore"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <Image>
            <Image.Source>
                <interop:D3DImage x:Name="D3DImage"></interop:D3DImage>
            </Image.Source>
        </Image>
    </Grid>
</Window>

为了等待窗口等初始化完成,将在 Loaded 时进行实际的初始化代码

代码语言:javascript复制
        public MainWindow()
        {
            InitializeComponent();

            Loaded  = MainWindow_Loaded;
        }

在 MainWindow_Loaded 上添加本文的关键逻辑

按照顺序,先创建 D3D11 设备和初始化。开始前,考虑到命名空间十分复杂,为了方便理解,先定义引用,如以下代码

代码语言:javascript复制
using Silk.NET.Core.Native;

using D3D11 = Silk.NET.Direct3D11;
using D3D9 = Silk.NET.Direct3D9;
using DXGI = Silk.NET.DXGI;
using D2D = SharpDX.Direct2D1;
using SharpDXDXGI = SharpDX.DXGI;
using SharpDXMathematics = SharpDX.Mathematics.Interop;

虽然加上此命名空间引用会让代码写的时候,稍微复杂一点,但好在清晰

定义完成之后,开始创建 D3D11 设备。 创建过程中,需要先设置参数,代码如下

代码语言:javascript复制
        private void MainWindow_Loaded(object sender, RoutedEventArgs e)
        {
            // 根据 [Surface sharing between Windows graphics APIs - Win32 apps](https://docs.microsoft.com/en-us/windows/win32/direct3darticles/surface-sharing-between-windows-graphics-apis?WT.mc_id=WD-MVP-5003260 ) 文档

            var width = ImageWidth;
            var height = ImageHeight;

            // 2021.12.23 不能在 x86 下运行,会炸掉。参阅 https://github.com/dotnet/Silk.NET/issues/731

            var texture2DDesc = new D3D11.Texture2DDesc()
            {
                BindFlags = (uint) (D3D11.BindFlag.BindRenderTarget | D3D11.BindFlag.BindShaderResource),
                Format = DXGI.Format.FormatB8G8R8A8Unorm, // 最好使用此格式,否则还需要后续转换
                Width = (uint) width,
                Height = (uint) height,
                MipLevels = 1,
                SampleDesc = new DXGI.SampleDesc(1, 0),
                Usage = D3D11.Usage.UsageDefault,
                MiscFlags = (uint) D3D11.ResourceMiscFlag.ResourceMiscShared,
                // The D3D11_RESOURCE_MISC_FLAG cannot be used when creating resources with D3D11_CPU_ACCESS flags.
                CPUAccessFlags = 0, //(uint) D3D11.CpuAccessFlag.None,
                ArraySize = 1
            };

            // 忽略代码
        }
        private int ImageWidth => (int) ActualWidth;
        private int ImageHeight => (int) ActualHeight;

需要特别说明以上代码的一个注释,当前 Silk.NET 对 X86 的支持较弱,调试模式下运行将会炸掉应用,非调试模式下没啥问题。其原因是 Silk.NET 对于 COM 封装在定义上是不对的,我给官方报告了此问题,请看 https://github.com/dotnet/Silk.NET/issues/731

问题的原因是在 Silk.NET 里面,定义对 DirectX 的调用,使用的是 Cdecl 方式调用,然而在 DirectX 的定义里,需要采用 Stdcall 来调用才是正确的。此行为将在 X86 下导致调用栈的内容不对,本应该清理的内容没有正确清理。这部分细节请参阅 stdcall Microsoft Docs 和 cdecl Microsoft Docs 官方文档

创建参数里,为了方便在 WPF 里使用,要求最好使用 FormatB8G8R8A8Unorm 格式。以上参数差不多是固定写法,各个参数的细节请看 DirectX 官方文档

接下来通过 D3D11 类型的 GetApi 方法获取 D3D11 对象,此对象的获取是 Silk.NET 的封装,不属于 DirectX 的内容

代码语言:javascript复制
            D3D11.D3D11 d3D11 = D3D11.D3D11.GetApi();

因为 Silk.NET 的封装特别底层,需要开启不安全代码才能创建对象,为了方便编写代码,将在 class 上加上 unsafe 让此类的所有代码在使用不安全代码,不需要再加上 unsafe 即可使用

代码语言:javascript复制
    public unsafe partial class MainWindow : Window
    {
    }

创建 D3D11 设备的代码如下

代码语言:javascript复制
            D3D11.ID3D11Device* pD3D11Device;
            D3D11.ID3D11DeviceContext* pD3D11DeviceContext;
            D3DFeatureLevel pD3DFeatureLevel = default;

            var hr = d3D11.CreateDevice((DXGI.IDXGIAdapter*) IntPtr.Zero, D3DDriverType.D3DDriverTypeHardware,
                Software: 0,
                Flags: (uint) D3D11.CreateDeviceFlag.CreateDeviceBgraSupport,
                (D3DFeatureLevel*) IntPtr.Zero,
                FeatureLevels: 0, // D3DFeatureLevel 的长度
                SDKVersion: 7,
                (D3D11.ID3D11Device**) &pD3D11Device, // 参阅 [C# 从零开始写 SharpDx 应用 聊聊功能等级](https://blog.lindexi.com/post/C-从零开始写-SharpDx-应用-聊聊功能等级.html )
                ref pD3DFeatureLevel,
                (D3D11.ID3D11DeviceContext**) &pD3D11DeviceContext
            );
            SilkMarshal.ThrowHResult(hr);

可以看到代码里面大量用到不安全代码

在创建完成了 D3D11 设备之后,即可开始创建 Texture 对象。咱的步骤是创建出 Texture 用来共享和给 D2D 绘制用,但 D2D 绘制在的是 Texture 的 IDXGISurface 平面上

创建 Texture2D 代码如下

代码语言:javascript复制
            D3D11.ID3D11Texture2D* pD3D11Texture2D;
            hr = pD3D11Device->CreateTexture2D(ref texture2DDesc, (D3D11.SubresourceData*) IntPtr.Zero, &pD3D11Texture2D);
            SilkMarshal.ThrowHResult(hr);

此 ID3D11Texture2D 就是作为后续 D2D 绘制的 IDXGISurface 对象

代码语言:javascript复制
            var renderTarget = pD3D11Texture2D;
            DXGI.IDXGISurface* pDXGISurface;
            var dxgiSurfaceGuid = DXGI.IDXGISurface.Guid;
            renderTarget->QueryInterface(ref dxgiSurfaceGuid, (void**) &pDXGISurface);

接下来部分就是 SharpDx 的啦,当前 Silk.NET 还没有封装好 D2D 部分,于是这里就和 WPF 使用 SharpDX 博客的方法差不多,只是创建 SharpDX 的 Surface 代码稍微修改而已

代码语言:javascript复制
            var surface = new SharpDXDXGI.Surface(new IntPtr((void*) pDXGISurface));

其他逻辑如下

代码语言:javascript复制
            var d2DFactory = new D2D.Factory();

            var renderTargetProperties =
                new D2D.RenderTargetProperties(new D2D.PixelFormat(SharpDXDXGI.Format.Unknown, D2D.AlphaMode.Premultiplied));
            _d2DRenderTarget = new D2D.RenderTarget(d2DFactory, surface, renderTargetProperties);

        private D2D.RenderTarget _d2DRenderTarget;

拿到了 D2D.RenderTarget 就可以进行 D2D 绘制。但是在开始前,还需要关联到 WPF 的 D3DImage 才能渲染。为了关联 D3DImage 就需要继续创建 D3D9 设备,如下面代码,调用 SetRenderTarget 将 D3D11 创建的 ID3D11Texture2D 作为 D3D9 的共享纹理,从而让 D2D 的内容可以在 D3DImage 上使用

代码语言:javascript复制
            SetRenderTarget(renderTarget);

在 SetRenderTarget 的代码是从 ID3D11Texture2D 转到 IDirect3DSurface9 上,将 IDirect3DSurface9 作为 D3DImage 的 BackBuffer 给 WPF 使用

代码语言:javascript复制
        private void SetRenderTarget(D3D11.ID3D11Texture2D* target)
        {
        }

从 ID3D11Texture2D 转到 IDirect3DSurface9 上有如下步骤:

  • 获取共享指针
  • 创建 D3D9 设备
  • 通过 D3D9 设备,使用共享指针创建纹理,通过纹理获取平面

获取共享指针是为了让 D3D9 的纹理共享 D3D11 的资源,获取代码如下

代码语言:javascript复制
            DXGI.IDXGIResource* pDXGIResource;
            var dxgiResourceGuid = DXGI.IDXGIResource.Guid;
            target->QueryInterface(ref dxgiResourceGuid, (void**) &pDXGIResource);

            void* sharedHandle;
            var hr = pDXGIResource->GetSharedHandle(&sharedHandle);
            SilkMarshal.ThrowHResult(hr);

创建 D3D9 之前,需要使用 Silk.NET 的 D3D9 类的 GetApi 对象获取 D3D9 对象。这是 Silk.NET 的设计,可以看到此库很多类型都有 GetApi 方法

代码语言:javascript复制
            var d3d9 = D3D9.D3D9.GetApi();

创建 D3D9 设备之前,需要先创建 IDirect3D9Ex 对象

代码语言:javascript复制
            D3D9.IDirect3D9Ex* pDirect3D9Ex;
            hr = d3d9.Direct3DCreate9Ex(SDKVersion: 32, &pDirect3D9Ex);
            SilkMarshal.ThrowHResult(hr);
            var d3DContext = pDirect3D9Ex;

创建 D3D9 设备之前,也需要初始化参数,有一些参数需要和 D3D11 创建的参数相同,需要先获取 D3D11 的参数

代码语言:javascript复制
            D3D11.Texture2DDesc texture2DDescription = default;
            target->GetDesc(ref texture2DDescription);

初始化创建 D3D9 的创建参数

代码语言:javascript复制
            var presentParameters = new D3D9.PresentParameters()
            {
                Windowed = 1,// true
                SwapEffect = D3D9.Swapeffect.SwapeffectDiscard,
                HDeviceWindow = GetDesktopWindow(),
                PresentationInterval = D3D9.D3D9.PresentIntervalDefault,
            };

            // 设置使用多线程方式,这样的性能才足够
            uint createFlags = D3D9.D3D9.CreateHardwareVertexprocessing | D3D9.D3D9.CreateMultithreaded | D3D9.D3D9.CreateFpuPreserve;

        [DllImport("user32.dll", SetLastError = false)]
        public static extern IntPtr GetDesktopWindow();

拿到创建参数,创建 D3D9 设备

代码语言:javascript复制
            D3D9.IDirect3DDevice9Ex* pDirect3DDevice9Ex;
            hr = d3DContext->CreateDeviceEx(Adapter: 0, 
                DeviceType: D3D9.Devtype.DevtypeHal,// 使用硬件渲染
                hFocusWindow: IntPtr.Zero, 
                createFlags,
                ref presentParameters, 
                pFullscreenDisplayMode: (D3D9.Displaymodeex*) IntPtr.Zero, 
                &pDirect3DDevice9Ex);
            SilkMarshal.ThrowHResult(hr);

            var d3DDevice = pDirect3DDevice9Ex;

拿到 D3D9 设备,开始创建纹理

代码语言:javascript复制
            D3D9.IDirect3DTexture9* pDirect3DTexture9;
            hr = d3DDevice->CreateTexture(texture2DDescription.Width, texture2DDescription.Height, Levels: 1,
                D3D9.D3D9.UsageRendertarget, 
                D3D9.Format.FmtA8R8G8B8, // 这是必须要求的颜色,不能使用其他颜色
                D3D9.Pool.PoolDefault, 
                &pDirect3DTexture9,
                &sharedHandle);
            SilkMarshal.ThrowHResult(hr);
            _renderTarget = pDirect3DTexture9;

        private D3D9.IDirect3DTexture9* _renderTarget;

纹理有要求颜色格式,也要求尺寸和 D3D11 的相同

通过纹理可以拿到 IDirect3DSurface9 对象

代码语言:javascript复制
            D3D9.IDirect3DSurface9* pDirect3DSurface9;
            _renderTarget->GetSurfaceLevel(0, &pDirect3DSurface9);
            _pDirect3DSurface9 = pDirect3DSurface9;

将 IDirect3DSurface9 作为 D3DImage 的 BackBuffer 即可完成初始化

代码语言:javascript复制
            D3DImage.Lock();
            D3DImage.SetBackBuffer(D3DResourceType.IDirect3DSurface9, new IntPtr(pDirect3DSurface9));
            D3DImage.Unlock();

在 MainWindow_Loaded 设置将一个视图数组绑定到管道的光栅化阶段

代码语言:javascript复制
            var viewport = new D3D11.Viewport(0, 0, width, height, 0, 1);
            pD3D11DeviceContext->RSSetViewports(NumViewports: 1, ref viewport);

开始测试 D2D 的渲染,通过测试 D2D 即可了解是否创建初始化成功。在 WPF 的 CompositionTarget 的 Rendering 进行 D2D 绘制

代码语言:javascript复制
            CompositionTarget.Rendering  = CompositionTarget_Rendering;

        private void CompositionTarget_Rendering(object? sender, EventArgs e)
        {
            _d2DRenderTarget.BeginDraw();

            OnRender(_d2DRenderTarget);

            _d2DRenderTarget.EndDraw();

            D3DImage.Lock();

            D3DImage.AddDirtyRect(new Int32Rect(0, 0, D3DImage.PixelWidth, D3DImage.PixelHeight));

            D3DImage.Unlock();
        }

在 OnRender 方法加上 D2D 的绘制内容,这就是测试逻辑,请根据自己的需求编写

代码语言:javascript复制
        private void OnRender(D2D.RenderTarget renderTarget)
        {
            var brush = new D2D.SolidColorBrush(_d2DRenderTarget, new SharpDXMathematics.RawColor4(1, 0, 0, 1));

            renderTarget.Clear(null);

            renderTarget.DrawRectangle(new SharpDXMathematics.RawRectangleF(_x, _y, _x   10, _y   10), brush);

            _x = _x   _dx;
            _y = _y   _dy;
            if (_x >= ActualWidth - 10 || _x <= 0)
            {
                _dx = -_dx;
            }

            if (_y >= ActualHeight - 10 || _y <= 0)
            {
                _dy = -_dy;
            }
        }

        private float _x;
        private float _y;
        private float _dx = 1;
        private float _dy = 1;

按照微软官方的推荐,在 CompositionTarget_Rendering 里,如果进行 DirectX 的逻辑,需要判断是否进入了多次,但本文这里只是测试逻辑,忽略官方给出的逻辑

运行代码即可看到界面上有一个矩形显示

也许后续我会封装一个 Silk.NET 的 DirectX 给 WPF 使用的控件

代码语言:javascript复制
#nullable disable

using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Interop;
using System.Windows.Media;

using Silk.NET.Core.Native;

using D3D11 = Silk.NET.Direct3D11;
using D3D9 = Silk.NET.Direct3D9;
using DXGI = Silk.NET.DXGI;
using D2D = SharpDX.Direct2D1;
using SharpDXDXGI = SharpDX.DXGI;
using SharpDXMathematics = SharpDX.Mathematics.Interop;

namespace RawluharkewalQeaninanel
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public unsafe partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

            Loaded  = MainWindow_Loaded;
        }

        private void MainWindow_Loaded(object sender, RoutedEventArgs e)
        {
            // 根据 [Surface sharing between Windows graphics APIs - Win32 apps](https://docs.microsoft.com/en-us/windows/win32/direct3darticles/surface-sharing-between-windows-graphics-apis?WT.mc_id=WD-MVP-5003260 ) 文档

            var width = ImageWidth;
            var height = ImageHeight;

            // 2021.12.23 不能在 x86 下运行,会炸掉。参阅 https://github.com/dotnet/Silk.NET/issues/731

            var texture2DDesc = new D3D11.Texture2DDesc()
            {
                BindFlags = (uint) (D3D11.BindFlag.BindRenderTarget | D3D11.BindFlag.BindShaderResource),
                Format = DXGI.Format.FormatB8G8R8A8Unorm, // 最好使用此格式,否则还需要后续转换
                Width = (uint) width,
                Height = (uint) height,
                MipLevels = 1,
                SampleDesc = new DXGI.SampleDesc(1, 0),
                Usage = D3D11.Usage.UsageDefault,
                MiscFlags = (uint) D3D11.ResourceMiscFlag.ResourceMiscShared,
                // The D3D11_RESOURCE_MISC_FLAG cannot be used when creating resources with D3D11_CPU_ACCESS flags.
                CPUAccessFlags = 0, //(uint) D3D11.CpuAccessFlag.None,
                ArraySize = 1
            };

            D3D11.ID3D11Device* pD3D11Device;
            D3D11.ID3D11DeviceContext* pD3D11DeviceContext;
            D3DFeatureLevel pD3DFeatureLevel = default;
            D3D11.D3D11 d3D11 = D3D11.D3D11.GetApi();

            var hr = d3D11.CreateDevice((DXGI.IDXGIAdapter*) IntPtr.Zero, D3DDriverType.D3DDriverTypeHardware,
                Software: 0,
                Flags: (uint) D3D11.CreateDeviceFlag.CreateDeviceBgraSupport,
                (D3DFeatureLevel*) IntPtr.Zero,
                FeatureLevels: 0, // D3DFeatureLevel 的长度
                SDKVersion: 7,
                (D3D11.ID3D11Device**) &pD3D11Device, // 参阅 [C# 从零开始写 SharpDx 应用 聊聊功能等级](https://blog.lindexi.com/post/C-从零开始写-SharpDx-应用-聊聊功能等级.html )
                ref pD3DFeatureLevel,
                (D3D11.ID3D11DeviceContext**) &pD3D11DeviceContext
            );
            SilkMarshal.ThrowHResult(hr);

            Debugger.Launch();
            Debugger.Break();

            _pD3D11Device = pD3D11Device;
            _pD3D11DeviceContext = pD3D11DeviceContext;

            D3D11.ID3D11Texture2D* pD3D11Texture2D;
            hr = pD3D11Device->CreateTexture2D(ref texture2DDesc, (D3D11.SubresourceData*) IntPtr.Zero, &pD3D11Texture2D);
            SilkMarshal.ThrowHResult(hr);

            var renderTarget = pD3D11Texture2D;
            _pD3D11Texture2D = pD3D11Texture2D;

            DXGI.IDXGISurface* pDXGISurface;
            var dxgiSurfaceGuid = DXGI.IDXGISurface.Guid;
            renderTarget->QueryInterface(ref dxgiSurfaceGuid, (void**) &pDXGISurface);
            _pDXGISurface = pDXGISurface;

            var d2DFactory = new D2D.Factory();

            var renderTargetProperties =
                new D2D.RenderTargetProperties(new D2D.PixelFormat(SharpDXDXGI.Format.Unknown, D2D.AlphaMode.Premultiplied));
            var surface = new SharpDXDXGI.Surface(new IntPtr((void*) pDXGISurface));
            _d2DRenderTarget = new D2D.RenderTarget(d2DFactory, surface, renderTargetProperties);

            SetRenderTarget(renderTarget);

            var viewport = new D3D11.Viewport(0, 0, width, height, 0, 1);
            pD3D11DeviceContext->RSSetViewports(NumViewports: 1, ref viewport);

            CompositionTarget.Rendering  = CompositionTarget_Rendering;
        }

        private void CompositionTarget_Rendering(object? sender, EventArgs e)
        {
            _d2DRenderTarget.BeginDraw();

            OnRender(_d2DRenderTarget);

            _d2DRenderTarget.EndDraw();

            D3DImage.Lock();

            D3DImage.AddDirtyRect(new Int32Rect(0, 0, D3DImage.PixelWidth, D3DImage.PixelHeight));

            D3DImage.Unlock();
        }

        private void OnRender(D2D.RenderTarget renderTarget)
        {
            var brush = new D2D.SolidColorBrush(_d2DRenderTarget, new SharpDXMathematics.RawColor4(1, 0, 0, 1));

            renderTarget.Clear(null);

            renderTarget.DrawRectangle(new SharpDXMathematics.RawRectangleF(_x, _y, _x   10, _y   10), brush);

            _x = _x   _dx;
            _y = _y   _dy;
            if (_x >= ActualWidth - 10 || _x <= 0)
            {
                _dx = -_dx;
            }

            if (_y >= ActualHeight - 10 || _y <= 0)
            {
                _dy = -_dy;
            }
        }

        private float _x;
        private float _y;
        private float _dx = 1;
        private float _dy = 1;

        private void SetRenderTarget(D3D11.ID3D11Texture2D* target)
        {
            DXGI.IDXGIResource* pDXGIResource;
            var dxgiResourceGuid = DXGI.IDXGIResource.Guid;
            target->QueryInterface(ref dxgiResourceGuid, (void**) &pDXGIResource);

            D3D11.Texture2DDesc texture2DDescription = default;
            target->GetDesc(ref texture2DDescription);

            void* sharedHandle;
            var hr = pDXGIResource->GetSharedHandle(&sharedHandle);
            SilkMarshal.ThrowHResult(hr);

            var d3d9 = D3D9.D3D9.GetApi();
            D3D9.IDirect3D9Ex* pDirect3D9Ex;
            hr = d3d9.Direct3DCreate9Ex(SDKVersion: 32, &pDirect3D9Ex);
            SilkMarshal.ThrowHResult(hr);
            var d3DContext = pDirect3D9Ex;
            _pDirect3D9Ex = pDirect3D9Ex;

            var presentParameters = new D3D9.PresentParameters()
            {
                Windowed = 1,// true
                SwapEffect = D3D9.Swapeffect.SwapeffectDiscard,
                HDeviceWindow = GetDesktopWindow(),
                PresentationInterval = D3D9.D3D9.PresentIntervalDefault,
            };

            // 设置使用多线程方式,这样的性能才足够
            uint createFlags = D3D9.D3D9.CreateHardwareVertexprocessing | D3D9.D3D9.CreateMultithreaded | D3D9.D3D9.CreateFpuPreserve;

            D3D9.IDirect3DDevice9Ex* pDirect3DDevice9Ex;
            hr = d3DContext->CreateDeviceEx(Adapter: 0, 
                DeviceType: D3D9.Devtype.DevtypeHal,// 使用硬件渲染
                hFocusWindow: IntPtr.Zero, 
                createFlags,
                ref presentParameters, 
                pFullscreenDisplayMode: (D3D9.Displaymodeex*) IntPtr.Zero, 
                &pDirect3DDevice9Ex);
            SilkMarshal.ThrowHResult(hr);

            var d3DDevice = pDirect3DDevice9Ex;

            D3D9.IDirect3DTexture9* pDirect3DTexture9;
            hr = d3DDevice->CreateTexture(texture2DDescription.Width, texture2DDescription.Height, Levels: 1,
                D3D9.D3D9.UsageRendertarget, 
                D3D9.Format.FmtA8R8G8B8, // 这是必须要求的颜色,不能使用其他颜色
                D3D9.Pool.PoolDefault, 
                &pDirect3DTexture9,
                &sharedHandle);
            SilkMarshal.ThrowHResult(hr);
            _renderTarget = pDirect3DTexture9;

            D3D9.IDirect3DSurface9* pDirect3DSurface9;
            _renderTarget->GetSurfaceLevel(0, &pDirect3DSurface9);
            _pDirect3DSurface9 = pDirect3DSurface9;

            D3DImage.Lock();
            D3DImage.SetBackBuffer(D3DResourceType.IDirect3DSurface9, new IntPtr(pDirect3DSurface9));
            D3DImage.Unlock();
        }

        // 这些字段的另一个作用是防止回收
        private D2D.RenderTarget _d2DRenderTarget;

        private D3D11.ID3D11Device* _pD3D11Device;
        private D3D11.ID3D11DeviceContext* _pD3D11DeviceContext;
        private D3D11.ID3D11Texture2D* _pD3D11Texture2D;
        private DXGI.IDXGISurface* _pDXGISurface;

        private D3D9.IDirect3D9Ex* _pDirect3D9Ex;
        private D3D9.IDirect3DTexture9* PDirect3DTexture9 => _renderTarget;
        private D3D9.IDirect3DTexture9* _renderTarget;
        private D3D9.IDirect3DSurface9* _pDirect3DSurface9;

        private int ImageWidth => (int) ActualWidth;
        private int ImageHeight => (int) ActualHeight;

        [DllImport("user32.dll", SetLastError = false)]
        public static extern IntPtr GetDesktopWindow();
    }
}

本文所有代码放在github 和 gitee 欢迎访问

可以通过如下方式获取本文的源代码,先创建一个空文件夹,接着使用命令行 cd 命令进入此空文件夹,在命令行里面输入以下代码,即可获取到本文的代码

代码语言:javascript复制
git init
git remote add origin https://gitee.com/lindexi/lindexi_gd.git
git pull origin f4c2f884b3fb006676aeef7e249055c5e2d8766d

以上使用的是 gitee 的源,如果 gitee 不能访问,请替换为 github 的源

代码语言:javascript复制
git remote remove origin
git remote add origin https://github.com/lindexi/lindexi_gd.git

获取代码之后,进入 RawluharkewalQeaninanel 文件夹

本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。欢迎转载、使用、重新发布,但务必保留文章署名林德熙(包含链接:https://blog.lindexi.com),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请与我联系。

0 人点赞